From 9184bef6f852c65c8246885bea3811dea719f843 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 18:25:02 +0100 Subject: [PATCH 01/10] Transient mode for auto-measure ruler --- src/ant/ant/antService.cc | 159 ++++++++++++++++++++----- src/ant/ant/antService.h | 50 +++++++- src/laybasic/laybasic/layMove.cc | 5 +- src/laybasic/laybasic/laySelector.h | 9 +- src/laybasic/laybasic/layViewObject.cc | 10 +- src/laybasic/laybasic/layViewObject.h | 16 +++ 6 files changed, 200 insertions(+), 49 deletions(-) diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index f7506bea6..e1cc12f97 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1056,8 +1056,18 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view) m_drawing (false), m_current (), m_move_mode (MoveNone), m_seg_index (0), - m_current_template (0) -{ + m_current_template (0), + m_hover (false), + m_hover_wait (false), + m_hover_buttons (0), + m_mouse_in_window (false) +{ +#if defined(HAVE_QT) + m_timer.setInterval (100 /*hover time*/); + m_timer.setSingleShot (true); + connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ())); +#endif + mp_view->annotations_changed_event.add (this, &Service::annotations_changed); } @@ -1809,9 +1819,36 @@ Service::mouse_double_click_event (const db::DPoint & /*p*/, unsigned int button return false; } +lay::TwoPointSnapToObjectResult +Service::auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl) +{ + // for auto-metric we need some cutline constraint - any or global won't do. + if (ac == lay::AC_Global) { + ac = tpl.angle_constraint (); + } + if (ac == lay::AC_Global) { + ac = m_snap_mode; + } + if (ac == lay::AC_Global) { + ac = lay::AC_Diagonal; + } + + db::DVector g; + if (m_grid_snap) { + g = db::DVector (m_grid, m_grid); + } + + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (m_snap_range); + snap_range *= 0.5; + + return lay::obj_snap2 (mp_view, p, g, ac, snap_range, snap_range * 1000.0); +} + bool Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { + hover_reset (); + if (prio && (buttons & lay::LeftButton) != 0) { const ant::Template &tpl = current_template (); @@ -1852,27 +1889,7 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio } else if (tpl.mode () == ant::Template::RulerAutoMetric) { - // for auto-metric we need some cutline constraint - any or global won't do. - lay::angle_constraint_type ac = ac_from_buttons (buttons); - if (ac == lay::AC_Global) { - ac = tpl.angle_constraint (); - } - if (ac == lay::AC_Global) { - ac = m_snap_mode; - } - if (ac == lay::AC_Global) { - ac = lay::AC_Diagonal; - } - - db::DVector g; - if (m_grid_snap) { - g = db::DVector (m_grid, m_grid); - } - - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (m_snap_range); - snap_range *= 0.5; - - lay::TwoPointSnapToObjectResult ee = lay::obj_snap2 (mp_view, p, g, ac, snap_range, snap_range * 1000.0); + lay::TwoPointSnapToObjectResult ee = auto_measure (p, ac_from_buttons (buttons), tpl); if (ee.any) { // begin the transaction @@ -1968,21 +1985,33 @@ Service::create_measure_ruler (const db::DPoint &pt, lay::angle_constraint_type bool Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) { - if (prio) { + if (! prio) { + return false; + } - lay::PointSnapToObjectResult snap_details; - if (m_drawing) { - snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)); - } else { - const ant::Template &tpl = current_template (); - snap_details = snap1_details (p, m_obj_snap && tpl.snap ()); - } + if (! m_drawing && m_mouse_in_window && view ()->transient_selection_mode ()) { - mouse_cursor_from_snap_details (snap_details); + // Restart hover timer + m_hover_wait = true; +#if defined(HAVE_QT) + m_timer.start (); +#endif + m_hover_point = p; + m_hover_buttons = buttons; } - if (m_drawing && prio) { + lay::PointSnapToObjectResult snap_details; + if (m_drawing) { + snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)); + } else { + const ant::Template &tpl = current_template (); + snap_details = snap1_details (p, m_obj_snap && tpl.snap ()); + } + + mouse_cursor_from_snap_details (snap_details); + + if (m_drawing) { set_cursor (lay::Cursor::cross); @@ -2284,6 +2313,70 @@ Service::click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mo } } +bool +Service::enter_event (bool /*prio*/) +{ + m_mouse_in_window = true; + return false; +} + +bool +Service::leave_event (bool) +{ + m_mouse_in_window = false; + hover_reset (); + return false; +} + +void +Service::hover_reset () +{ + if (m_hover_wait) { +#if defined(HAVE_QT) + m_timer.stop (); +#endif + m_hover_wait = false; + } + if (m_hover) { + // as we use the transient selection for the hover ruler, we have to remove it here + clear_transient_selection (); + m_hover = false; + } +} + +#if defined(HAVE_QT) +void +Service::timeout () +{ + m_hover_wait = false; + m_hover = true; + + // as we use the transient selection for the hover ruler, we have to remove it here + clear_transient_selection (); + + // transiently create an auto-metric ruler if requested + + const ant::Template &tpl = current_template (); + if (tpl.mode () == ant::Template::RulerAutoMetric) { + + lay::TwoPointSnapToObjectResult ee = auto_measure (m_hover_point, ac_from_buttons (m_hover_buttons), tpl); + if (ee.any) { + + m_current = ant::Object (ee.first, ee.second, 0, tpl); + + // HINT: there is no special style for "transient selection on rulers" + mp_transient_ruler = new ant::View (this, &m_current, true /*not selected*/); + + if (! editables ()->has_selection ()) { + display_status (true); + } + + } + + } +} +#endif + bool Service::transient_select (const db::DPoint &pos) { diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index eaf664726..fb3ed67fe 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -39,6 +39,11 @@ #include #include +#if defined (HAVE_QT) +# include +# include +#endif + namespace ant { class LayoutViewBase; @@ -177,12 +182,19 @@ private: // ------------------------------------------------------------- -class ANT_PUBLIC Service - : public lay::EditorServiceBase, +class ANT_PUBLIC Service : +#if defined (HAVE_QT) + public QObject, +#endif + public lay::EditorServiceBase, public lay::Drawing, public db::Object { -public: +#if defined (HAVE_QT) +Q_OBJECT +#endif + +public: typedef lay::AnnotationShapes::iterator obj_iterator; /** @@ -341,6 +353,21 @@ public: */ virtual db::DBox selection_bbox (); + /** + * @brief Implementation of the editables API + */ + virtual bool enter_event (bool); + + /** + * @brief Implementation of the editables API + */ + virtual bool leave_event (bool); + + /** + * @brief Implementation of the editables API + */ + virtual void hover_reset (); + /** * @brief Transform the selection (reimplementation of lay::Editable interface) */ @@ -506,6 +533,11 @@ public: */ tl::Event annotation_selection_changed_event; +#if defined (HAVE_QT) +public slots: + void timeout (); +#endif + private: // Ruler display and snapping configuration tl::Color m_color; @@ -551,10 +583,22 @@ private: std::vector m_ruler_templates; unsigned int m_current_template; + // Hover detector + bool m_hover; + bool m_hover_wait; + db::DPoint m_hover_point; + unsigned int m_hover_buttons; +#if defined (HAVE_QT) + QTimer m_timer; +#endif + + bool m_mouse_in_window; + std::pair snap1 (const db::DPoint &p, bool obj_snap); lay::PointSnapToObjectResult snap1_details (const db::DPoint &p, bool obj_snap); std::pair snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::PointSnapToObjectResult snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); + lay::TwoPointSnapToObjectResult auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl); const ant::Template ¤t_template () const; diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index ab6c4d6d6..4bc22e7d0 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -305,10 +305,7 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_ if (mp_editables->begin_move (p, ac_from_buttons (buttons))) { - lay::SelectionService *selector = mp_view->selection_service (); - if (selector) { - selector->hover_reset (); - } + ui ()->hover_reset (); mp_view->clear_transient_selection (); diff --git a/src/laybasic/laybasic/laySelector.h b/src/laybasic/laybasic/laySelector.h index a47789158..6ad7600a9 100644 --- a/src/laybasic/laybasic/laySelector.h +++ b/src/laybasic/laybasic/laySelector.h @@ -69,14 +69,7 @@ public: virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); - - /** - * @brief Reset the hover timer for the transient selection - * - * This method may be used by other services (in particular Move) to avoid the transient to - * be triggered from a move operation. - */ - void hover_reset (); + virtual void hover_reset (); #if defined (HAVE_QT) public slots: diff --git a/src/laybasic/laybasic/layViewObject.cc b/src/laybasic/laybasic/layViewObject.cc index 0927fd261..d794fe87f 100644 --- a/src/laybasic/laybasic/layViewObject.cc +++ b/src/laybasic/laybasic/layViewObject.cc @@ -1051,7 +1051,15 @@ ViewObjectUI::drag_cancel () } } -namespace +void +ViewObjectUI::hover_reset () +{ + for (service_iterator svc = begin_services (); svc != end_services (); ++svc) { + (*svc)->hover_reset (); + } +} + +namespace { struct z_order_compare_f { diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index 76f9b5175..8ba1bebb9 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -147,6 +147,17 @@ public: virtual bool drop_event (const db::DPoint & /*p*/, const DragDropDataBase * /*data*/) { return false; } #endif + /** + * @brief Hover reset request + * + * This event is issued for services providing some "hover" mode - i.e. capture + * mouse move events and start a timer on them. + * + * The implementation of this event should cancel this timer and + * not raise a hover condition. + */ + virtual void hover_reset () { } + /** * @brief Mouse press event handler * @@ -605,6 +616,11 @@ public: */ void drag_cancel (); + /** + * @brief Calls hover_reset on all services + */ + void hover_reset (); + /** * @brief CanvasPlane rendering * From 1dbb3917c86fd39d23c1a8e1e33c51f43fdbddb4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 19:02:51 +0100 Subject: [PATCH 02/10] Edge-measure rulers too. --- src/ant/ant/RulerConfigPage4.ui | 11 +++++-- src/ant/ant/antConfig.cc | 4 +++ src/ant/ant/antPlugin.cc | 3 ++ src/ant/ant/antService.cc | 53 ++++++++++++++++++++++++++++----- src/ant/ant/antTemplate.h | 9 ++++-- src/ant/ant/gsiDeclAnt.cc | 11 +++++++ 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/ant/ant/RulerConfigPage4.ui b/src/ant/ant/RulerConfigPage4.ui index 7e2c2365c..87e5dc3cc 100644 --- a/src/ant/ant/RulerConfigPage4.ui +++ b/src/ant/ant/RulerConfigPage4.ui @@ -6,8 +6,8 @@ 0 0 - 668 - 410 + 745 + 438 @@ -845,7 +845,12 @@ - Angle measurement (three mouse clicks) + Auto measure along edge (points will be set automatically) + + + + + Angle or radius measurement (three mouse clicks) diff --git a/src/ant/ant/antConfig.cc b/src/ant/ant/antConfig.cc index 36576b00a..5ed7c7d0b 100644 --- a/src/ant/ant/antConfig.cc +++ b/src/ant/ant/antConfig.cc @@ -247,6 +247,8 @@ RulerModeConverter::to_string (ant::Template::ruler_mode_type m) return "single_click"; } else if (m == ant::Template::RulerAutoMetric) { return "auto_metric"; + } else if (m == ant::Template::RulerAutoMetricEdge) { + return "auto_metric_edge"; } else if (m == ant::Template::RulerMultiSegment) { return "multi_segment"; } else if (m == ant::Template::RulerThreeClicks) { @@ -266,6 +268,8 @@ RulerModeConverter::from_string (const std::string &s, ant::Template::ruler_mode a = ant::Template::RulerSingleClick; } else if (t == "auto_metric") { a = ant::Template::RulerAutoMetric; + } else if (t == "auto_metric_edge") { + a = ant::Template::RulerAutoMetricEdge; } else if (t == "multi_segment") { a = ant::Template::RulerMultiSegment; } else if (t == "angle") { diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index 86a14d11a..06680c2a8 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -60,6 +60,9 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Measure")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure")); templates.back ().set_mode (ant::Template::RulerAutoMetric); + templates.push_back (ant::Template (tl::to_string (tr ("Measure edge")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure_edge")); + templates.back ().set_mode (ant::Template::RulerAutoMetricEdge); + templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); templates.back ().set_mode (ant::Template::RulerThreeClicks); diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index e1cc12f97..11b0d2fb7 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1910,6 +1910,29 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio } + } else if (tpl.mode () == ant::Template::RulerAutoMetricEdge) { + + lay::PointSnapToObjectResult snap_details = snap1_details (p, true); + if (snap_details.object_snap == lay::PointSnapToObjectResult::ObjectEdge) { + + // begin the transaction + if (manager ()) { + tl_assert (! manager ()->transacting ()); + manager ()->transaction (tl::to_string (tr ("Create ruler"))); + } + + m_current = ant::Object (snap_details.object_ref.p1 (), snap_details.object_ref.p2 (), 0, tpl); + show_message (); + + insert_ruler (m_current, true); + + // end the transaction + if (manager ()) { + manager ()->commit (); + } + + } + } else { m_p1 = snap1 (p, m_obj_snap && tpl.snap ()).second; @@ -2006,7 +2029,7 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) snap_details = snap2_details (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)); } else { const ant::Template &tpl = current_template (); - snap_details = snap1_details (p, m_obj_snap && tpl.snap ()); + snap_details = snap1_details (p, m_obj_snap && tpl.snap () && (tpl.mode () != ant::Template::RulerAutoMetricEdge || ! view ()->transient_selection_mode ())); } mouse_cursor_from_snap_details (snap_details); @@ -2356,24 +2379,38 @@ Service::timeout () // transiently create an auto-metric ruler if requested + ant::Object *ruler = 0; + const ant::Template &tpl = current_template (); if (tpl.mode () == ant::Template::RulerAutoMetric) { lay::TwoPointSnapToObjectResult ee = auto_measure (m_hover_point, ac_from_buttons (m_hover_buttons), tpl); if (ee.any) { - m_current = ant::Object (ee.first, ee.second, 0, tpl); + ruler = &m_current; + } - // HINT: there is no special style for "transient selection on rulers" - mp_transient_ruler = new ant::View (this, &m_current, true /*not selected*/); - - if (! editables ()->has_selection ()) { - display_status (true); - } + } else if (tpl.mode () == ant::Template::RulerAutoMetricEdge) { + lay::PointSnapToObjectResult snap_details = snap1_details (m_hover_point, true); + if (snap_details.object_snap == lay::PointSnapToObjectResult::ObjectEdge) { + m_current = ant::Object (snap_details.object_ref.p1 (), snap_details.object_ref.p2 (), 0, tpl); + ruler = &m_current; } } + + if (ruler) { + + // HINT: there is no special style for "transient selection on rulers" + mp_transient_ruler = new ant::View (this, ruler, true /*not selected*/); + + if (! editables ()->has_selection ()) { + display_status (true); + } + + } + } #endif diff --git a/src/ant/ant/antTemplate.h b/src/ant/ant/antTemplate.h index f589cc91e..6310ce2c6 100644 --- a/src/ant/ant/antTemplate.h +++ b/src/ant/ant/antTemplate.h @@ -64,15 +64,20 @@ public: */ RulerAutoMetric = 2, + /** + * @brief The ruler is auto-metric along an edge: a single click will place a ruler and the ruler will extend to the edge below + */ + RulerAutoMetricEdge = 3, + /** * @brief The ruler an angle type (two segments, three mouse clicks) for angle and circle radius measurements */ - RulerThreeClicks = 3, + RulerThreeClicks = 4, /** * @brief The ruler is a multi-segment type */ - RulerMultiSegment = 4 + RulerMultiSegment = 5 }; /** diff --git a/src/ant/ant/gsiDeclAnt.cc b/src/ant/ant/gsiDeclAnt.cc index d7e709318..6961636e3 100644 --- a/src/ant/ant/gsiDeclAnt.cc +++ b/src/ant/ant/gsiDeclAnt.cc @@ -434,6 +434,11 @@ static int ruler_mode_auto_metric () return ant::Template::RulerAutoMetric; } +static int ruler_mode_auto_metric_edge () +{ + return ant::Template::RulerAutoMetricEdge; +} + static int ruler_mode_three_clicks () { return ant::Template::RulerThreeClicks; @@ -525,6 +530,12 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "\n" "This constant has been introduced in version 0.25" ) + + gsi::method ("RulerModeAutoMetricEdge", &gsi::ruler_mode_auto_metric_edge, + "@brief Specifies edge-sensitive auto-metric ruler mode for the \\register_template method\n" + "In auto-metric mode, a ruler can be placed with a single click and p1/p2 will be determined from the edge it is placed on.\n" + "\n" + "This constant has been introduced in version 0.29" + ) + gsi::method ("RulerThreeClicks", &gsi::ruler_mode_three_clicks, "@brief Specifies three-click ruler mode for the \\register_template method\n" "In this ruler mode, two segments are created for angle and circle radius measurements. Three mouse clicks are required.\n" From 596c6c0aacc0ca1a944aac3706e3bd542c49b902 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 22:10:33 +0100 Subject: [PATCH 03/10] Grayscale rasterizer for Region --- src/db/db/gsiDeclDbRegion.cc | 65 +++++++++++++++++++++++++++++++++++ testdata/ruby/dbRegionTest.rb | 30 ++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 43b809553..1bef00823 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -780,6 +780,39 @@ size_dvm (db::Region *region, const db::Vector &dv, unsigned int mode) return *region; } +static std::vector > +rasterize2 (const db::Region *region, const db::Point &origin, const db::Vector &pixel_distance, const db::Vector &pixel_size, unsigned int nx, unsigned int ny) +{ + db::AreaMap am (origin, pixel_distance, pixel_size, nx, ny); + + auto si = region->begin (); + si = si.confined (am.bbox (), false /*not overlapping*/); + + while (! si.at_end ()) { + db::rasterize (*si, am); + ++si; + } + + std::vector > result; + result.reserve (ny); + for (unsigned int y = 0; y < ny; ++y) { + result.push_back (std::vector ()); + std::vector &row = result.back (); + row.reserve (nx); + for (unsigned int x = 0; x < nx; ++x) { + row.push_back (am.get (x, y)); + } + } + + return result; +} + +static std::vector > +rasterize1 (const db::Region *region, const db::Point &origin, const db::Vector &pixel_size, unsigned int nx, unsigned int ny) +{ + return rasterize2 (region, origin, pixel_size, pixel_size, nx, ny); +} + static db::Point default_origin; // provided by gsiDeclDbPolygon.cc: @@ -3071,6 +3104,38 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "@brief Converts the region to a string\n" "This version allows specification of the maximum number of polygons contained in the string." ) + + method_ext ("rasterize", &rasterize1, gsi::arg ("origin"), gsi::arg ("pixel_size"), gsi::arg ("nx"), gsi::arg ("ny"), + "@brief A grayscale rasterizer delivering the area covered per pixel\n" + "@param origin The lower-left corner of the lowest-left pixel\n" + "@param pixel_size The dimension of each pixel (the x component gives the width, the y component the height)\n" + "@param nx The number of pixels in horizontal direction\n" + "@param ny The number of pixels in vertical direction\n" + "The method will create a grayscale, high-resolution density map of a rectangular region.\n" + "The scan region is defined by the origin, the pixel size and the number of pixels in horizontal (nx) and\n" + "vertical (ny) direction. The resulting array will contain the area covered by polygons from the region\n" + "in square database units.\n" + "\n" + "For non-overlapping polygons, the maximum density value is px*py. Overlapping polygons are counted multiple\n" + "times, so the actual values may be larger. If you want overlaps removed, you have to\n" + "merge the region before. Merge semantics does not apply for the 'rasterize' method.\n" + "\n" + "Although the resulting area values are floating-point, internal computation is done with integer precision currently.\n" + "This implies a certain area error when skew angle edges are involved. The pixel area is precise for Manhattan and\n"" + "half-Manhattan (45 degree multiples) input geometries.\n" + "\n" + "A second version exists that allows specifying an active pixel size which is smaller than the\n" + "pixel distance hence allowing pixels samples that do not cover the full area, but leave gaps between the pixels.\n" + "\n" + "This method has been added in version 0.29.\n" + ) + + method_ext ("rasterize", &rasterize2, gsi::arg ("origin"), gsi::arg ("pixel_distance"), gsi::arg ("pixel_size"), gsi::arg ("nx"), gsi::arg ("ny"), + "@brief A version of 'rasterize' that allows a pixel step distance which is larger than the pixel size\n" + "This version behaves like the first variant of 'rasterize', but the pixel distance (pixel-to-pixel step raster)\n" + "can be specified separately from the pixel size. Currently, the pixel size must be equal or smaller than the\n" + "pixel distance - i.e. the pixels must not overlap.\n" + "\n" + "This method has been added in version 0.29.\n" + ) + method ("enable_progress", &db::Region::enable_progress, gsi::arg ("label"), "@brief Enable progress reporting\n" "After calling this method, the region will report the progress through a progress bar while " diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index de204bc3d..bbf38548c 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1188,6 +1188,36 @@ class DBRegion_TestClass < TestBase end + # rasterize + def test_rasterize + + r = RBA::Region::new() + r.insert(RBA::Polygon::new([[0, 0], [100, 100], [200, 0]])) + r.insert(RBA::Polygon::new(RBA::Box::new([0, 200], [100, 300]))) + + pd = RBA::Vector::new(50, 50) + ps = RBA::Vector::new(25, 25) + + sum = 0 + 2.times do |ix| + 2.times do |iy| + am = r.rasterize(RBA::Point::new(-50 + ix * ps.x, -20 + iy * ps.y), pd, ps, 7, 7) + sum += am.collect { |r| r.sum }.sum + end + end + + assert_equal(sum, 8.0 * pd.x * pd.y) + + tot = 0.0 + pd = RBA::Vector::new(50, 50) + + am = r.rasterize(RBA::Point::new(-50, -20), pd, 7, 7) + sum = am.collect { |r| r.sum }.sum + + assert_equal(sum, 8.0 * pd.x * pd.y) + + end + end load("test_epilogue.rb") From 7634c77c23b1c96453132f774023178c76cc6fcc Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 23:18:49 +0100 Subject: [PATCH 04/10] Generalization of polygon rasterizer to DPolygon for higher precision of pixel area values. --- src/db/db/dbPolygonTools.cc | 196 +++++++++++++++++++++++----------- src/db/db/dbPolygonTools.h | 55 ++++++---- src/db/db/gsiDeclDbRegion.cc | 17 ++- testdata/ruby/dbRegionTest.rb | 6 +- 4 files changed, 179 insertions(+), 95 deletions(-) diff --git a/src/db/db/dbPolygonTools.cc b/src/db/db/dbPolygonTools.cc index a59b03903..4ccb7dd6b 100644 --- a/src/db/db/dbPolygonTools.cc +++ b/src/db/db/dbPolygonTools.cc @@ -1566,23 +1566,26 @@ compute_rounded (const db::DPolygon &polygon, double rinner, double router, unsi } // ------------------------------------------------------------------------- -// Implementation of AreaMap +// Implementation of area_map -AreaMap::AreaMap () +template +area_map::area_map () : m_nx (0), m_ny (0) { mp_av = 0; } -AreaMap::AreaMap (const AreaMap &other) +template +area_map::area_map (const area_map &other) : m_nx (0), m_ny (0) { mp_av = 0; operator= (other); } -AreaMap & -AreaMap::operator= (const AreaMap &other) +template +area_map & +area_map::operator= (const area_map &other) { if (this != &other) { // TODO: this could be copy on write @@ -1594,21 +1597,24 @@ AreaMap::operator= (const AreaMap &other) return *this; } -AreaMap::AreaMap (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny) +template +area_map::area_map (const area_map::point_type &p0, const area_map::vector_type &d, size_t nx, size_t ny) : m_p0 (p0), m_d (d), m_p (d), m_nx (nx), m_ny (ny) { mp_av = new area_type [nx * ny]; clear (); } -AreaMap::AreaMap (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny) +template +area_map::area_map (const area_map::point_type &p0, const area_map::vector_type &d, const area_map::vector_type &p, size_t nx, size_t ny) : m_p0 (p0), m_d (d), m_p (std::min (d.x (), p.x ()), std::min (d.y (), p.y ())), m_nx (nx), m_ny (ny) { mp_av = new area_type [nx * ny]; clear (); } -AreaMap::~AreaMap () +template +area_map::~area_map () { if (mp_av) { delete[] mp_av; @@ -1616,18 +1622,20 @@ AreaMap::~AreaMap () mp_av = 0; } +template void -AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny) +area_map::reinitialize (const area_map::point_type &p0, const area_map::vector_type &d, size_t nx, size_t ny) { reinitialize (p0, d, d, nx, ny); } +template void -AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny) +area_map::reinitialize (const area_map::point_type &p0, const area_map::vector_type &d, const area_map::vector_type &p, size_t nx, size_t ny) { m_p0 = p0; m_d = d; - m_p = db::Vector (std::min (d.x (), p.x ()), std::min (d.y (), p.y ())); + m_p = vector_type (std::min (d.x (), p.x ()), std::min (d.y (), p.y ())); if (nx != m_nx || ny != m_ny) { @@ -1645,8 +1653,9 @@ AreaMap::reinitialize (const db::Point &p0, const db::Vector &d, const db::Vecto clear (); } +template void -AreaMap::clear () +area_map::clear () { if (mp_av) { area_type *a = mp_av; @@ -1656,8 +1665,9 @@ AreaMap::clear () } } +template void -AreaMap::swap (AreaMap &other) +area_map::swap (area_map &other) { std::swap (m_p0, other.m_p0); std::swap (m_d, other.m_d); @@ -1667,8 +1677,9 @@ AreaMap::swap (AreaMap &other) std::swap (mp_av, other.mp_av); } -AreaMap::area_type -AreaMap::total_area () const +template +typename area_map::area_type +area_map::total_area () const { area_type asum = 0; if (mp_av) { @@ -1680,16 +1691,21 @@ AreaMap::total_area () const return asum; } -db::Box -AreaMap::bbox () const +template +typename area_map::box_type +area_map::bbox () const { if (m_nx == 0 || m_ny == 0) { - return db::Box (); + return box_type (); } else { - return db::Box (m_p0, m_p0 + db::Vector (db::Coord (m_nx - 1) * m_d.x () + m_p.x (), db::Coord (m_ny - 1) * m_d.y () + m_p.y ())); + return box_type (m_p0, m_p0 + vector_type (C (m_nx - 1) * m_d.x () + m_p.x (), C (m_ny - 1) * m_d.y () + m_p.y ())); } } +// explicit instantiations +template class area_map; +template class area_map; + // ------------------------------------------------------------------------- // Implementation of rasterize @@ -1707,29 +1723,69 @@ static bool edge_is_partially_left_of (const db::Edge &e, const db::Edge &e_orig } } -bool -rasterize (const db::Polygon &polygon, db::AreaMap &am) +static bool edge_is_partially_left_of (const db::DEdge &e, const db::DEdge &e_original, db::DCoord x) { - typedef db::AreaMap::area_type area_type; - db::Box box = am.bbox (); - db::Box pbox = polygon.box (); + DCoord xmin = db::edge_xmin (e); + if (db::coord_traits::less (xmin, x)) { + return true; + } else if (db::coord_traits::equal (xmin, x) && ! db::coord_traits::equal (e_original.dx (), 0)) { + // the skew edge is cut partially rendering a straight vertical line (due to rounding) + // which we will count as "left of" + return true; + } else { + return false; + } +} + +static size_t npixels_floor (db::Coord d, db::Coord p) +{ + return size_t (std::max (db::Coord (0), d / p)); +} + +static size_t npixels_ceil (db::Coord d, db::Coord p) +{ + return size_t (std::max (db::Coord (0), (d + p - 1) / p)); +} + +static size_t npixels_floor (db::DCoord d, db::DCoord p) +{ + return size_t (std::max (db::DCoord (0), floor (d / p + db::epsilon))); +} + +static size_t npixels_ceil (db::DCoord d, db::DCoord p) +{ + return size_t (std::max (db::DCoord (0), ceil (d / p - db::epsilon))); +} + + +template +static +bool +rasterize_impl (const db::polygon &polygon, db::area_map &am) +{ + typedef typename db::area_map::area_type area_type; + typedef db::box box_type; + typedef db::edge edge_type; + + box_type box = am.bbox (); + box_type pbox = polygon.box (); // check if the polygon overlaps the rasterization area. Otherwise, we simply do nothing. if (! pbox.overlaps (box)) { return false; } - db::Coord ymin = box.bottom (), ymax = box.top (); - db::Coord dy = am.d ().y (), dx = am.d ().x (); - db::Coord py = am.p ().y (), px = am.p ().x (); - db::Coord y0 = am.p0 ().y (), x0 = am.p0 ().x (); + C ymin = box.bottom (), ymax = box.top (); + C dy = am.d ().y (), dx = am.d ().x (); + C py = am.p ().y (), px = am.p ().x (); + C y0 = am.p0 ().y (), x0 = am.p0 ().x (); size_t ny = am.ny (), nx = am.nx (); - size_t iy0 = std::min (ny, size_t (std::max (db::Coord (0), (pbox.bottom () - am.p0 ().y ()) / am.d ().y ()))); - size_t iy1 = std::min (ny, size_t (std::max (db::Coord (0), (pbox.top () - am.p0 ().y () + am.d ().y () - 1) / am.d ().y ()))); + size_t iy0 = std::min (ny, npixels_floor (pbox.bottom () - am.p0 ().y (), am.d ().y ())); + size_t iy1 = std::min (ny, npixels_ceil (pbox.top () - am.p0 ().y (), am.d ().y ())); - size_t ix0 = std::min (nx, size_t (std::max (db::Coord (0), (pbox.left () - am.p0 ().x ()) / am.d ().x ()))); - size_t ix1 = std::min (nx, size_t (std::max (db::Coord (0), (pbox.right () - am.p0 ().x () + am.d ().x () - 1) / am.d ().x ()))); + size_t ix0 = std::min (nx, npixels_floor (pbox.left () - am.p0 ().x (), am.d ().x ())); + size_t ix1 = std::min (nx, npixels_ceil (pbox.right () - am.p0 ().x (), am.d ().x ())); // no scanning required (i.e. degenerated polygon) -> do nothing if (iy0 == iy1 || ix0 == ix1) { @@ -1738,26 +1794,26 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) // collect edges size_t n = 0; - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + for (typename db::polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { if ((*e).dy () != 0 && db::edge_ymax (*e) > ymin && db::edge_ymin (*e) < ymax) { ++n; } } - std::vector edges; + std::vector edges; edges.reserve (n); - for (db::Polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { + for (typename db::polygon::polygon_edge_iterator e = polygon.begin_edge (); ! e.at_end (); ++e) { if ((*e).dy () != 0 && db::edge_ymax (*e) > ymin && db::edge_ymin (*e) < ymax) { edges.push_back (*e); } } // sort edges - std::sort (edges.begin (), edges.end (), db::edge_ymin_compare ()); + std::sort (edges.begin (), edges.end (), db::edge_ymin_compare ()); - std::vector ::iterator c = edges.begin (); + typename std::vector ::iterator c = edges.begin (); - db::Coord y = y0 + dy * db::Coord (iy0); + C y = y0 + dy * C (iy0); while (c != edges.end () && db::edge_ymax (*c) <= y) { ++c; @@ -1767,36 +1823,36 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) return false; } - std::vector ::iterator f = c; + typename std::vector ::iterator f = c; for (size_t iy = iy0; iy < iy1; ++iy) { - db::Coord yy = y + py; + C yy = y + py; while (f != edges.end () && db::edge_ymin (*f) < yy) { ++f; } - std::sort (c, f, db::edge_xmin_compare ()); + std::sort (c, f, db::edge_xmin_compare ()); - db::Coord x = x0 + dx * db::Coord (ix0); - db::Coord xl = pbox.left (); + C x = x0 + dx * C (ix0); + C xl = pbox.left (); area_type a = 0; - std::vector ::iterator cc = c; + typename std::vector ::iterator cc = c; while (cc != edges.end () && cc != f && db::edge_xmax (*cc) <= x) { - db::Coord y1 = std::max (y, std::min (yy, cc->p1 ().y ())); - db::Coord y2 = std::max (y, std::min (yy, cc->p2 ().y ())); + C y1 = std::max (y, std::min (yy, cc->p1 ().y ())); + C y2 = std::max (y, std::min (yy, cc->p2 ().y ())); a += area_type (px) * area_type (y2 - y1); ++cc; } - std::vector ::iterator ff = cc; + typename std::vector ::iterator ff = cc; for (size_t ix = ix0; ix < ix1; ++ix) { - db::Coord xx = x + px; - db::Coord xxx = x + dx; + C xx = x + px; + C xxx = x + dx; // TODO: edge_xmin_at_interval(y, yy) and edge_xmax.. would be more efficient in the // all-angle case. However, it is crucial that the edge clipping produces @@ -1807,7 +1863,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) ++ff; } - std::vector ::iterator fff = ff; + typename std::vector ::iterator fff = ff; if (xx < xxx) { while (fff != f && db::edge_xmin (*fff) < xxx) { @@ -1818,11 +1874,11 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) if (xl < x) { // consider all edges or parts of those left of the first cell - db::Box left (xl, y, x, yy); + box_type left (xl, y, x, yy); - for (std::vector ::iterator e = cc; e != ff; ++e) { + for (typename std::vector ::iterator e = cc; e != ff; ++e) { - std::pair ec = e->clipped (left); + std::pair ec = e->clipped (left); if (ec.first && edge_is_partially_left_of (ec.second, *e, x)) { a += area_type (ec.second.dy ()) * area_type (px); } @@ -1835,11 +1891,11 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) if (dx == py) { - db::Box cell (x, y, xx, yy); + box_type cell (x, y, xx, yy); - for (std::vector ::iterator e = cc; e != ff; ++e) { + for (typename std::vector ::iterator e = cc; e != ff; ++e) { - std::pair ec = e->clipped (cell); + std::pair ec = e->clipped (cell); if (ec.first && edge_is_partially_left_of (ec.second, *e, xx)) { aa += (area_type (ec.second.dy ()) * area_type (2 * xx - (ec.second.p2 ().x () + ec.second.p1 ().x ()))) / 2; a += area_type (ec.second.dy ()) * area_type (px); @@ -1849,22 +1905,22 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) } else { - db::Box cell (x, y, xx, yy); + box_type cell (x, y, xx, yy); - for (std::vector ::iterator e = cc; e != ff; ++e) { + for (typename std::vector ::iterator e = cc; e != ff; ++e) { - std::pair ec = e->clipped (cell); + std::pair ec = e->clipped (cell); if (ec.first && edge_is_partially_left_of (ec.second, *e, xx)) { aa += (area_type (ec.second.dy ()) * area_type (2 * xx - (ec.second.p2 ().x () + ec.second.p1 ().x ()))) / 2; } } - db::Box wide_cell (x, y, x + dx, yy); + box_type wide_cell (x, y, x + dx, yy); - for (std::vector ::iterator e = cc; e != fff; ++e) { + for (typename std::vector ::iterator e = cc; e != fff; ++e) { - std::pair wide_ec = e->clipped (wide_cell); + std::pair wide_ec = e->clipped (wide_cell); if (wide_ec.first && edge_is_partially_left_of (wide_ec.second, *e, x + dx)) { a += area_type (wide_ec.second.dy ()) * area_type (px); } @@ -1880,7 +1936,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) ff = fff; - for (std::vector ::iterator ccx = cc; ccx != ff; ++ccx) { + for (typename std::vector ::iterator ccx = cc; ccx != ff; ++ccx) { if (db::edge_xmax (*ccx) <= x) { std::swap (*ccx, *cc); ++cc; @@ -1898,7 +1954,7 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) y = yy; - for (std::vector ::iterator cx = c; cx != f; ++cx) { + for (typename std::vector ::iterator cx = c; cx != f; ++cx) { if (db::edge_ymax (*cx) <= y) { std::swap (*cx, *c); ++c; @@ -1910,6 +1966,18 @@ rasterize (const db::Polygon &polygon, db::AreaMap &am) return true; } +bool +rasterize (const db::Polygon &polygon, db::AreaMap &am) +{ + return rasterize_impl (polygon, am); +} + +bool +rasterize (const db::DPolygon &polygon, db::DAreaMap &am) +{ + return rasterize_impl (polygon, am); +} + // ------------------------------------------------------------------------- // Implementation of Minkowski sum diff --git a/src/db/db/dbPolygonTools.h b/src/db/db/dbPolygonTools.h index e931aa54e..87497812f 100644 --- a/src/db/db/dbPolygonTools.h +++ b/src/db/db/dbPolygonTools.h @@ -493,55 +493,59 @@ bool DB_PUBLIC is_non_orientable_polygon (const db::Polygon &poly, std::vector +class DB_PUBLIC area_map { public: - typedef db::coord_traits::area_type area_type; + typedef typename db::coord_traits::area_type area_type; + typedef db::point point_type; + typedef db::vector vector_type; + typedef db::box box_type; /** * @brief Constructor */ - AreaMap (); + area_map (); /** * @brief Copy constructor */ - AreaMap (const AreaMap &); + area_map (const area_map &); /** * @brief Constructor */ - AreaMap (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny); + area_map (const point_type &p0, const vector_type &d, size_t nx, size_t ny); /** * @brief Constructor with pixel size */ - AreaMap (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny); + area_map (const point_type &p0, const vector_type &d, const vector_type &p, size_t nx, size_t ny); /** * @brief Destructor */ - ~AreaMap (); + ~area_map (); /** * @brief Assignment */ - AreaMap &operator= (const AreaMap &); + area_map &operator= (const area_map &); /** * @brief Reinitialize */ - void reinitialize (const db::Point &p0, const db::Vector &d, size_t nx, size_t ny); + void reinitialize (const point_type &p0, const vector_type &d, size_t nx, size_t ny); /** * @brief Reinitialize with pixel size */ - void reinitialize (const db::Point &p0, const db::Vector &d, const db::Vector &p, size_t nx, size_t ny); + void reinitialize (const point_type &p0, const vector_type &d, const vector_type &p, size_t nx, size_t ny); /** * @brief Swap of two maps */ - void swap (AreaMap &other); + void swap (area_map &other); /** * @brief Get the area of one pixel @@ -578,7 +582,7 @@ public: /** * @brief The origin */ - const db::Point &p0 () const + const point_type &p0 () const { return m_p0; } @@ -586,7 +590,7 @@ public: /** * @brief Move the origin */ - void move (const db::Vector &d) + void move (const vector_type &d) { m_p0 += d; } @@ -594,7 +598,7 @@ public: /** * @brief The per-pixel displacement vector (pixel size) */ - const db::Vector &d () const + const vector_type &d () const { return m_d; } @@ -602,7 +606,7 @@ public: /** * @brief The pixel size (must be less than d) */ - const db::Vector &p () const + const vector_type &p () const { return m_p; } @@ -610,7 +614,7 @@ public: /** * @brief Compute the bounding box of the area map */ - db::Box bbox () const; + box_type bbox () const; /** * @brief Compute the total area @@ -632,12 +636,15 @@ public: private: area_type *mp_av; - db::Point m_p0; - db::Vector m_d; - db::Vector m_p; + point_type m_p0; + vector_type m_d; + vector_type m_p; size_t m_nx, m_ny; }; +typedef area_map AreaMap; +typedef area_map DAreaMap; + /** * @brief Rasterize the polygon into the given area map * @@ -648,6 +655,16 @@ private: */ bool DB_PUBLIC rasterize (const db::Polygon &polygon, db::AreaMap &am); +/** + * @brief Rasterize the polygon into the given area map (double version) + * + * This will decompose the polygon and produce per-pixel area values for the given + * polygon. The area contributions will be added to the given area map. + * + * Returns a value indicating whether the map will be non-empty. + */ +bool DB_PUBLIC rasterize (const db::DPolygon &polygon, db::DAreaMap &am); + /** * @brief Minkowski sum of an edge and a polygon */ diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 1bef00823..9314d332d 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -783,14 +783,15 @@ size_dvm (db::Region *region, const db::Vector &dv, unsigned int mode) static std::vector > rasterize2 (const db::Region *region, const db::Point &origin, const db::Vector &pixel_distance, const db::Vector &pixel_size, unsigned int nx, unsigned int ny) { - db::AreaMap am (origin, pixel_distance, pixel_size, nx, ny); + db::DAreaMap am (db::DPoint (origin), db::DVector (pixel_distance), db::DVector (pixel_size), nx, ny); - auto si = region->begin (); - si = si.confined (am.bbox (), false /*not overlapping*/); + auto pi = region->begin (); + pi = pi.confined (db::Box (am.bbox ()), false /*not overlapping*/); - while (! si.at_end ()) { - db::rasterize (*si, am); - ++si; + while (! pi.at_end ()) { + db::DPolygon dp (*pi); + db::rasterize (dp, am); + ++pi; } std::vector > result; @@ -3119,9 +3120,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "times, so the actual values may be larger. If you want overlaps removed, you have to\n" "merge the region before. Merge semantics does not apply for the 'rasterize' method.\n" "\n" - "Although the resulting area values are floating-point, internal computation is done with integer precision currently.\n" - "This implies a certain area error when skew angle edges are involved. The pixel area is precise for Manhattan and\n"" - "half-Manhattan (45 degree multiples) input geometries.\n" + "The resulting area values are precise within the limits of double-precision floating point arithmetics.\n" "\n" "A second version exists that allows specifying an active pixel size which is smaller than the\n" "pixel distance hence allowing pixels samples that do not cover the full area, but leave gaps between the pixels.\n" diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index bbf38548c..0c196a6bd 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1192,7 +1192,7 @@ class DBRegion_TestClass < TestBase def test_rasterize r = RBA::Region::new() - r.insert(RBA::Polygon::new([[0, 0], [100, 100], [200, 0]])) + r.insert(RBA::Polygon::new([[0, 0], [100, 100], [150, 0]])) r.insert(RBA::Polygon::new(RBA::Box::new([0, 200], [100, 300]))) pd = RBA::Vector::new(50, 50) @@ -1206,7 +1206,7 @@ class DBRegion_TestClass < TestBase end end - assert_equal(sum, 8.0 * pd.x * pd.y) + assert_equal("%.12g" % sum, "%.12g" % (7.0 * pd.x * pd.y)) tot = 0.0 pd = RBA::Vector::new(50, 50) @@ -1214,7 +1214,7 @@ class DBRegion_TestClass < TestBase am = r.rasterize(RBA::Point::new(-50, -20), pd, 7, 7) sum = am.collect { |r| r.sum }.sum - assert_equal(sum, 8.0 * pd.x * pd.y) + assert_equal("%.12g" % sum, "%.12g" % (7.0 * pd.x * pd.y)) end From 7a91a4fd429548a06d77f3d70c7d448010e19768 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 30 Jan 2024 00:11:30 +0100 Subject: [PATCH 05/10] [consider merging] Fixed rendering of color selector buttons on high-DPI screens --- src/layui/layui/layWidgets.cc | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/layui/layui/layWidgets.cc b/src/layui/layui/layWidgets.cc index cdc7a4e1b..792818c65 100644 --- a/src/layui/layui/layWidgets.cc +++ b/src/layui/layui/layWidgets.cc @@ -972,17 +972,31 @@ SimpleColorButton::set_color_internal (QColor c) m_color = c; QFontMetrics fm (font (), this); - QRect rt (fm.boundingRect (QObject::tr ("Auto"))); // dummy text to be compliant with the other color button - QPixmap pxmp (rt.width () + 24, rt.height ()); + QRect rt (fm.boundingRect (QObject::tr ("XXXXXXX"))); + +#if QT_VERSION >= 0x050000 + double dpr = devicePixelRatio (); +#else + double dpr = 1.0; +#endif + + QPixmap pxmp (rt.width () * dpr, rt.height () * dpr); +#if QT_VERSION >= 0x050000 + pxmp.setDevicePixelRatio (dpr); +#endif QPainter pxpainter (&pxmp); QColor text_color = palette ().color (QPalette::Active, QPalette::Text); - pxpainter.setPen (QPen (text_color)); pxpainter.setBrush (QBrush (c.isValid () ? c : QColor (128, 128, 128))); - QRect r (0, 0, pxmp.width () - 1, pxmp.height () - 1); + QPen frame_pen (text_color); + frame_pen.setWidthF (1.0); + frame_pen.setJoinStyle (Qt::MiterJoin); + pxpainter.setPen (frame_pen); + int dpri = int (dpr); + QRectF r ((dpri / 2) / dpr, (dpri / 2) / dpr, rt.width () - 1.0, rt.height () - 1.0); pxpainter.drawRect (r); - setIconSize (pxmp.size ()); + setIconSize (QSize (rt.width (), rt.height ())); setIcon (QIcon (pxmp)); } @@ -1216,22 +1230,25 @@ ColorButton::set_color_internal (QColor c) #if QT_VERSION >= 0x50000 pixmap.setDevicePixelRatio (dpr); #endif - pixmap.fill (QColor (0, 0, 0, 0)); QColor text_color = palette ().color (QPalette::Active, QPalette::Text); QPainter pxpainter (&pixmap); - pxpainter.setPen (QPen (text_color)); + QPen frame_pen (text_color); + frame_pen.setWidthF (1.0); + frame_pen.setJoinStyle (Qt::MiterJoin); + pxpainter.setPen (frame_pen); + + int dpri = int (dpr); + QRectF r ((dpri / 2) / dpr, (dpri / 2) / dpr, rt.width () - 1.0, rt.height () - 1.0); if (! m_color.isValid ()) { pxpainter.setFont (font ()); - QRectF r (0, 0, rt.width () - pxpainter.pen ().widthF (), rt.height () - pxpainter.pen ().widthF ()); pxpainter.drawText (r, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, QObject::tr ("Auto")); } else { pxpainter.setBrush (QBrush (c)); - QRectF r (0, 0, rt.width () - pxpainter.pen ().widthF (), rt.height () - pxpainter.pen ().widthF ()); pxpainter.drawRect (r); } From aef8161c82089dbf015f61db8b8885b03b974328 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 30 Jan 2024 00:11:56 +0100 Subject: [PATCH 06/10] [consider merging] cross-hair cursor should not use selection default line width and styles (halo etc.) --- src/laybasic/laybasic/layMouseTracker.cc | 23 +++++++++++++++-------- src/layui/layui/LayoutViewConfigPage2d.ui | 6 ------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/laybasic/laybasic/layMouseTracker.cc b/src/laybasic/laybasic/layMouseTracker.cc index 4de45d0f3..3c531e29b 100644 --- a/src/laybasic/laybasic/layMouseTracker.cc +++ b/src/laybasic/laybasic/layMouseTracker.cc @@ -105,15 +105,22 @@ MouseTracker::mouse_move_event (const db::DPoint &p, unsigned int /*buttons*/, b double max_coord = 1e30; // big enough I guess - mp_markers.push_back (new lay::DMarker (mp_view)); - mp_markers.back ()->set_line_style (m_cursor_line_style); - mp_markers.back ()->set_color (m_cursor_color); - mp_markers.back ()->set (db::DEdge (db::DPoint (tp.x (), -max_coord), db::DPoint (tp.x (), max_coord))); + for (int i = 0; i < 2; ++i) { - mp_markers.push_back (new lay::DMarker (mp_view)); - mp_markers.back ()->set_line_style (m_cursor_line_style); - mp_markers.back ()->set_color (m_cursor_color); - mp_markers.back ()->set (db::DEdge (db::DPoint (-max_coord, tp.y ()), db::DPoint (max_coord, tp.y ()))); + mp_markers.push_back (new lay::DMarker (mp_view)); + mp_markers.back ()->set_line_style (m_cursor_line_style); + mp_markers.back ()->set_line_width (1); + mp_markers.back ()->set_halo (false); + mp_markers.back ()->set_dither_pattern (1); + mp_markers.back ()->set_color (m_cursor_color.is_valid () ? m_cursor_color : mp_view->canvas ()->foreground_color ()); + + if (i == 0) { + mp_markers.back ()->set (db::DEdge (db::DPoint (tp.x (), -max_coord), db::DPoint (tp.x (), max_coord))); + } else { + mp_markers.back ()->set (db::DEdge (db::DPoint (-max_coord, tp.y ()), db::DPoint (max_coord, tp.y ()))); + } + + } } diff --git a/src/layui/layui/LayoutViewConfigPage2d.ui b/src/layui/layui/LayoutViewConfigPage2d.ui index ad779f091..f70f43881 100644 --- a/src/layui/layui/LayoutViewConfigPage2d.ui +++ b/src/layui/layui/LayoutViewConfigPage2d.ui @@ -55,9 +55,6 @@ - - The color in which the rulers are drawn - @@ -151,9 +148,6 @@ - - The color in which the rulers are drawn - From babf799016ddca5a74f90d8001e72e055aa28c7b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 10 Feb 2024 18:18:13 +0100 Subject: [PATCH 07/10] [consider merging] OASIS Reader: error on duplicate CELLNAMEs --- src/db/db/dbCommonReader.cc | 4 ++++ .../oasis/unit_tests/dbOASISReaderTests.cc | 18 ++++++++++++++++++ testdata/oasis/duplicate_cellname.oas | Bin 0 -> 1444 bytes 3 files changed, 22 insertions(+) create mode 100644 testdata/oasis/duplicate_cellname.oas diff --git a/src/db/db/dbCommonReader.cc b/src/db/db/dbCommonReader.cc index 64eb1d85f..c184501e9 100644 --- a/src/db/db/dbCommonReader.cc +++ b/src/db/db/dbCommonReader.cc @@ -154,6 +154,10 @@ CommonReaderBase::rename_cell (db::Layout &layout, size_t id, const std::string common_reader_error (tl::sprintf (tl::to_string (tr ("Cell named %s with ID %ld was already given name %s")), cn, id, iid->second.first)); } + if (iname != m_name_map.end () && iname->second.first != null_id && iname->second.first != id) { + common_reader_error (tl::sprintf (tl::to_string (tr ("Same cell name %s, but different IDs: %ld and %ld")), cn, id, iname->second.first)); + } + if (iid != m_id_map.end () && iname != m_name_map.end ()) { if (iname->second.second != iid->second.second) { diff --git a/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc b/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc index b87a413c1..f9e4bfefc 100644 --- a/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc +++ b/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc @@ -602,3 +602,21 @@ TEST(Bug_1474) EXPECT_EQ (ex.msg (), "Cell named ADDHX2 with ID 4 was already given name SEDFFTRX2 (position=763169, cell=)"); } } + +TEST(DuplicateCellname) +{ + db::Manager m (false); + db::Layout layout (&m); + + try { + tl::InputStream file (tl::testdata () + "/oasis/duplicate_cellname.oas"); + db::OASISReader reader (file); + reader.read (layout); + EXPECT_EQ (false, true); + } catch (tl::CancelException &ex) { + // Seen when private test data is not installed + throw; + } catch (tl::Exception &ex) { + EXPECT_EQ (ex.msg (), "Same cell name TOP, but different IDs: 3 and 0 (position=1070, cell=)"); + } +} diff --git a/testdata/oasis/duplicate_cellname.oas b/testdata/oasis/duplicate_cellname.oas new file mode 100644 index 0000000000000000000000000000000000000000..0ce5df9215310dddffc1a312f3158ea7d5b55f73 GIT binary patch literal 1444 zcmd^9&r1|h9Di@#do!~;j`rF&#-tufLp@k2L17B6rXU!WyP0*_!%7|c2X@({xdgH) zEoEh1l7tA{a-qnJ)Cv{-IJkktY7ibgl+4Burh74)H@jJ=b?Dl=%)HO%^S(n23d$=cci(fWm^MFW^BH`brakeJEjN=Ij<3yG7cQ z2%39Y-}z;B&vd|C=T~A_maS1J>p__0)c1Z4Ktf=6YGJ;sBzig2_ z?uYSW7Fs0zWkZKGt?>=qItl;^tRf$wp>YqJ2ML*#tnV%49tOlkqClRd;b$S<*(}V~ zu<%1+Yi2AwA#7*h8ro`rXG!eQCRA}yiXub5qkch#|Ie}V9Qdf?dpf>}^8y$CQ?l3X z5%SSWx&G5yG@owH!;IARd&b;lKI1Dpn|N;(vIqp(qW9|S9c$DR0T5JYut$KZKrI*- z5}4Ml9r!GGA`pTH#g_$6A)9(vWmHj1=mjcfK?JxF2FFB|u;6NLtBTaZqhg9^M+LAo zHKu9suBb|R5l#L@w}Kq4%z_`V(@J$wi{xsrplyq*{5i8K2tcYRRYdEOCGi%zEX^L8 z3ec=;6Tu>a^WF3=)Uz<+Z=1mySa=2T7sCNpf1yvw2KtQTwW>lbIp=2{Z||poTH=6c zHPf%|RG^w#3g1YoFVNE3(GlouYxA}G5%PDQDBT6*QUtyHx{eWS<~PG+gL_2s*tCr` zSJZNP#>Sij^8{gUx3|kVOb}_VIM(jtn{g2#Z%+I~M3V1BYfHPggEMIn5${jgO9{aq hj((B|>N#_kpi|wa1vn_f5f}WP`!#`#o$CLzegJ3%XM_L% literal 0 HcmV?d00001 From 1e1aa022517abae8c37f5c087e6114d66744dbb0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 10 Feb 2024 18:48:44 +0100 Subject: [PATCH 08/10] [consider merging] bugfix issue #1616 - typo in DRC doc --- src/doc/doc/about/drc_ref_global.xml | 2 +- src/drc/drc/built-in-macros/_drc_cop_integration.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/doc/about/drc_ref_global.xml b/src/doc/doc/about/drc_ref_global.xml index f18b0f0e7..670651628 100644 --- a/src/doc/doc/about/drc_ref_global.xml +++ b/src/doc/doc/about/drc_ref_global.xml @@ -828,7 +828,7 @@ The following example selects all shapes which are rectangles and whose area is larger than 0.5 square micrometers:

-out = in.drc(if_all(area > 0.5, rectangle))
+out = in.drc(if_all(area > 0.5, rectangles))
 

The condition expressions may be of any type (edges, edge pairs and polygons). diff --git a/src/drc/drc/built-in-macros/_drc_cop_integration.rb b/src/drc/drc/built-in-macros/_drc_cop_integration.rb index 8eebae6fb..7b617aab0 100644 --- a/src/drc/drc/built-in-macros/_drc_cop_integration.rb +++ b/src/drc/drc/built-in-macros/_drc_cop_integration.rb @@ -555,7 +555,7 @@ module DRC # whose area is larger than 0.5 square micrometers: # # @code - # out = in.drc(if_all(area > 0.5, rectangle)) + # out = in.drc(if_all(area > 0.5, rectangles)) # @/code # # The condition expressions may be of any type (edges, edge pairs and polygons). From ba61c4880f2376076dab117136006073bd938f26 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 11 Feb 2024 15:22:41 +0100 Subject: [PATCH 09/10] [consider merging] Update of testcase which was broken OAS --- .../foreigncell/au_ignore_foreign.oas.gz | Bin 906 -> 1493 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testdata/lefdef/foreigncell/au_ignore_foreign.oas.gz b/testdata/lefdef/foreigncell/au_ignore_foreign.oas.gz index c9dd21723297b02c121d520c30bd1668a62c76a4..907e6401611bfedbdf9ba20e545a14d60905272d 100644 GIT binary patch literal 1493 zcmd^-{ZrBh9LK*8;ui%(5(*R32vSF;BpP~Qu>_SxD&}m4v-yB_M){a{w$e&$Vk(W9 zn77V{)Ky+-cGc=?YChCVO`F&%F(MyY1t^~r_Id*AzBzr61Ap-1wBp`Kyf z$k0ffEeJC=dIM7kNLdA;ttfbyvdRh%PzZQ%aUm*jC|cUbw9WU%@?S7rn{LU}@d=(I z4J<e(HAU^HUXl3oPp1NS#$wU=v}+3Mu8Ex~ST!f!&rX~kbP#ju+}jV) zPKBYllaDXok8O2YpIbG2fC}g#EhsIGP3`a|7aHUSVgqbKWJ+0`G;8~bG4=&C@3@Rf z7PVHUxC(lid9MM@}kAihYyr@FdO^JNAIF#l9a&@60_>$qt*SA8Q*IhOhF{I|N(J zFVs45^>d%L(fyr&WC+U{#+CetD;RA~&gIfMi{@|^hyr7%fE)~eDVf9LOeS+?(uhPd z&YeiR`eF5Jlx&`P)yK!=+H%uBf4@txTeOR7nGz}Nd8kXZA0OzmW!d)YBs0EN517Vn z`wmoihXunDzw*^&41lvHK1Bgo9FACk5DTnvXksU0@rcAc1WLRFmBZz*Lmq&DX81M= zhY+T$l`sadtpOoo9s?MG`5HKQ8FlXkImF$)pc3|2<}X1c9E$MXnz+m(;Y&Dw1P<1~ zOSpR?M+ppylu{$AwZ`Ef;vd$zy5 zQqFo6URqCA`g7I}(6)4*@Y?$5XajeE+KD|V-uoc)naQ;$o5%Apjk56r9UcOy(#VO^F9@REkhf>9ye_2V zXz#Z_iYePw;B<|)s-os@`{{`((rE4LJi6la=CqX5Gpr;9>TT6Z)xv`JA&S}3J%+~t zRNx0_8szmaS(Cfc2j*TiXlO@x53^_f)24@bENX8Lst!=uIa6rjFYRq|<>549X?+M--F=X*V6^d@O8=o5g~(@k z_O}c#_W0p-_Le}z2d_F;UG-xmcE!_s?R^zzE5$1O2=h##n$b7RymR9UtHqr}%cykx k0`K?2xg;5@i;cGiAkc&c|AIgUCh19WY0E^lFT0Oe9kZyQw&52@B6_f5TP4%JmdWR3xw+hRT8o{! z({A@7VVPg5)w|0{iUNKnaSq1?wPI&yE4EoKnVGV6LkPW_Pv0nAx9n>r`jM(7jvB=M znDAp#6!W>6)J(0{s*9IRW5xM$C7PO?E0r^J#Kffdf@w6DJ1cglw&c)6Z>3?o_eoJV zjdtxTryEUu;KjG!NW7l7CCa9;R9lRuu3U*t$Hhyg(dvBebbGY6S!?$kQ8JB>TD6`h z5JRR>?|o9Mw>pbsqJ4p&4V-4vu75G=7<+5XeC#t<&zSa^CN73YOrz^Fu{Xuw=pB@Z zQS4fr@@)CXo8!-aTJje1Zo$iOWZj5k>&Ga8!TDU;?tS`Mt?Qugw;vOJOpfD)AI!mm z#1>*^2{~lsF_Ku?FowL#ogs-7;wVtYj}l*qPn)?S#Y8@br2xhG^h_pCZy;Z|Ndn!B ze6E$r{+tBe4r_zR~!n(|g(;ls!521-ou zCm^2C7(z;`4q*eMo_auZZFPz{#JPuI)COu-hY)2vwSJGd>bXPA zK12l~Pa&kD%Bq~otAZL(MK!2Os;nxis)p1{YFJOcN?3gp@4`A(pTNQca&vwg`fwM1 z$6fdy2k;%<+<`T22Zp>JV<&!wK7W?nhWo^=_?G){k5uoIEwA7XP@ViZumwM&xrtkF z6xfDM`~j^U{(#eC5V!*k{k^E6M;WTr9tYOAUHFFErxsQFuy6wVWB~KK@D$uhek0p( zgeSBq1jmI@^O(4V7HmkGp-WEiFQmQnXVdM_;_|cMp1>*XwT?%0z&}`p@twI$otgMo zXONZqut&b;tN~bk7}5vs3wz|hWrY|%5Dr3-NhSax59ze*kit?sVt gK}f;y3EU&6s8A(kHju3SpAW Date: Sun, 11 Feb 2024 16:22:42 +0100 Subject: [PATCH 10/10] Compatibility with older Ruby versions --- testdata/ruby/dbRegionTest.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 0c196a6bd..d1dd6615a 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1202,7 +1202,7 @@ class DBRegion_TestClass < TestBase 2.times do |ix| 2.times do |iy| am = r.rasterize(RBA::Point::new(-50 + ix * ps.x, -20 + iy * ps.y), pd, ps, 7, 7) - sum += am.collect { |r| r.sum }.sum + sum += am.collect { |r| r.inject(:+) }.inject(:+) end end @@ -1212,7 +1212,7 @@ class DBRegion_TestClass < TestBase pd = RBA::Vector::new(50, 50) am = r.rasterize(RBA::Point::new(-50, -20), pd, 7, 7) - sum = am.collect { |r| r.sum }.sum + sum = am.collect { |r| r.inject(:+) }.inject(:+) assert_equal("%.12g" % sum, "%.12g" % (7.0 * pd.x * pd.y))