diff --git a/src/img/img/ImagePropertiesPage.ui b/src/img/img/ImagePropertiesPage.ui index 2ea522bdd..fbb6b8a88 100644 --- a/src/img/img/ImagePropertiesPage.ui +++ b/src/img/img/ImagePropertiesPage.ui @@ -72,7 +72,6 @@ Sans Serif 12 - 75 false true false @@ -108,98 +107,31 @@ 6 - - - - h = - - - - - - - - - - x = - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Perspective - - - - - - - - - - + degree - - - - - - - - + + - Shear angle + h = - - - - Pixel Size - - + + - + degree - - - - micron - - - - - - - Rotation angle - - - - - - - micron - - - - + w = @@ -209,77 +141,8 @@ - - - - micron - - - - - - - Center - - - - - - - x = - - - - - - - Mirrored (at X-axis) - - - - - - - micron - - - - - - - - - - degree - - - - - - - Qt::Vertical - - - - 20 - 5 - - - - - - - - y = - - - - - - - degree - - + + @@ -292,15 +155,135 @@ 507 - 16 + 10 - - - + + + Qt::Vertical + + + + 20 + 5 + + + + + + + + (use them to align the image with the layout) + + + + + + + + + + + + + Rotation angle + + + + + + + x = + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + degree + + + + + + + Landmarks + + + + + + + micron + + + + + + + micron + + + + + + + Perspective + + + + + + + micron + + + + + + + + + + y = + + + + + + + Define + + + + + + + x = + + + + + + + micron + + + + + + + Shear angle + + + + Qt::Vertical @@ -316,32 +299,130 @@ - + + + + + + + Pixel Size + + + + + + + Mirrored (at X-axis) + + + + + + + degree + + + + + + + + + + Center + + + + y = - - + + + + + - Landmarks + Layer binding - - - - Define + + + + Qt::Vertical - + + QSizePolicy::Fixed + + + + 20 + 5 + + + - - - - (use them to align the image with the layout) + + + + + 0 + 0 + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Visibility of the image follows that layer's visibility + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -1111,6 +1192,11 @@ p, li { white-space: pre-wrap; }
imgWidgets.h
1 + + lay::LayerSelectionComboBox + QComboBox +
layWidgets.h
+
width_le diff --git a/src/img/img/gsiDeclImg.cc b/src/img/img/gsiDeclImg.cc index 549367b89..13c4e5987 100644 --- a/src/img/img/gsiDeclImg.cc +++ b/src/img/img/gsiDeclImg.cc @@ -811,13 +811,13 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "@brief Sets the mask from a array of boolean values\n" "The order of the boolean values is line first, from bottom to top and left to right and is the same as the order in the data array.\n" "\n" - "This method has been introduced in version 0.27.\n" + "This attribute has been introduced in version 0.27.\n" ) + gsi::method_ext ("mask_data", &get_mask_data, "@brief Gets the mask from a array of boolean values\n" "See \\set_mask_data for a description of the data field.\n" "\n" - "This method has been introduced in version 0.27.\n" + "This attribute has been introduced in version 0.27.\n" ) + gsi::method_ext ("pixel_width=", &img_set_pixel_width, gsi::arg ("w"), "@brief Sets the pixel width\n" @@ -826,7 +826,7 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "micron space with the transformation.\n" "\n" "Starting with version 0.22, this property is incorporated into the transformation matrix.\n" - "This property is provided for convenience only." + "This attribute is provided for convenience only." ) + gsi::method_ext ("pixel_width", &img_get_pixel_width, "@brief Gets the pixel width\n" @@ -834,7 +834,7 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "See \\pixel_width= for a description of that property.\n" "\n" "Starting with version 0.22, this property is incorporated into the transformation matrix.\n" - "This property is provided for convenience only." + "This attribute is provided for convenience only." ) + gsi::method_ext ("pixel_height=", &img_set_pixel_height, gsi::arg ("h"), "@brief Sets the pixel height\n" @@ -843,7 +843,7 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "micron space with the transformation.\n" "\n" "Starting with version 0.22, this property is incorporated into the transformation matrix.\n" - "This property is provided for convenience only." + "This attribute is provided for convenience only." ) + gsi::method_ext ("pixel_height", &img_get_pixel_height, "@brief Gets the pixel height\n" @@ -851,21 +851,35 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "See \\pixel_height= for a description of that property.\n" "\n" "Starting with version 0.22, this property is incorporated into the transformation matrix.\n" - "This property is provided for convenience only." + "This attribute is provided for convenience only." ) + gsi::method ("z_position", &ImageRef::z_position, "@brief Gets the z position of the image\n" "Images with a higher z position are painted in front of images with lower z position.\n" "The z value is an integer that controls the position relative to other images.\n" "\n" - "This method was introduced in version 0.25." + "This attribute was introduced in version 0.25." ) + gsi::method ("z_position=", &ImageRef::set_z_position, gsi::arg ("z"), "@brief Sets the z position of the image\n" "\n" "See \\z_position for details about the z position attribute.\n" "\n" - "This method was introduced in version 0.25." + "This attribute was introduced in version 0.25." + ) + + gsi::method ("layer_binding", &ImageRef::layer_binding, + "@brief Gets the layer binding of the image\n" + "If this attribute is set to a non-null layer properties object, the images visibility " + "is associated with that of this layer. If the layer becomes invisible, the image is hidden as well.\n" + "\n" + "This attribute was introduced in version 0.30.4." + ) + + gsi::method ("layer_binding=", &ImageRef::set_layer_binding, gsi::arg ("lp"), + "@brief Sets the layer binding of the image\n" + "\n" + "See \\layer_binding for details about the layer_binding attribute.\n" + "\n" + "This attribute was introduced in version 0.30.4." ) + gsi::method ("matrix=", &ImageRef::set_matrix, gsi::arg ("t"), "@brief Sets the transformation matrix\n" @@ -876,7 +890,7 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "The matrix is more general than the transformation used before and supports shear and perspective transformation. This property replaces the \\trans property which is " "still functional, but deprecated.\n" "\n" - "This method has been introduced in version 0.22." + "This attribute has been introduced in version 0.22." ) + gsi::method ("matrix", &ImageRef::matrix, "@brief Returns the pixel-to-micron transformation matrix\n" @@ -887,7 +901,7 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "The matrix is more general than the transformation used before and supports shear and perspective transformation. This property replaces the \\trans property which is " "still functional, but deprecated.\n" "\n" - "This method has been introduced in version 0.22." + "This attribute has been introduced in version 0.22." ) + gsi::method_ext ("trans", &img_get_trans, "@brief Returns the pixel-to-micron transformation\n" @@ -941,14 +955,14 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "\n" "See the \\is_visible? method for a description of this property.\n" "\n" - "This method has been introduced in version 0.20.\n" + "This attribute has been introduced in version 0.20.\n" ) + gsi::method ("is_visible?", &ImageRef::is_visible, "@brief Gets a flag indicating whether the image object is visible\n" "\n" "An image object can be made invisible by setting the visible property to false.\n" "\n" - "This method has been introduced in version 0.20.\n" + "This attribute has been introduced in version 0.20.\n" ) + gsi::method ("id", (size_t (ImageRef::*) () const) &ImageRef::id, "@brief Gets the Id\n" diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index ab76374f3..ae0b508c2 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -1020,6 +1020,7 @@ Object::operator= (const img::Object &d) m_visible = d.m_visible; m_z_position = d.m_z_position; + m_layer_binding = d.m_layer_binding; m_min_value = d.m_min_value; m_min_value_set = d.m_min_value_set; @@ -1052,6 +1053,10 @@ Object::less (const db::DUserObjectBase *d) const return m_z_position < img_object->m_z_position; } + if (m_layer_binding != img_object->m_layer_binding) { + return m_layer_binding < img_object->m_layer_binding; + } + double epsilon = (std::abs (m_min_value) + std::abs (m_max_value)) * 1e-6; if (std::abs (m_min_value - img_object->m_min_value) > epsilon) { return m_min_value < img_object->m_min_value; @@ -1102,7 +1107,11 @@ Object::operator== (const img::Object &d) const return false; } - // operator== is all fuzzy compare - + if (m_layer_binding != d.m_layer_binding) { + return false; + } + + // operator== is all fuzzy compare - double epsilon = (std::abs (m_min_value) + std::abs (m_max_value)) * 1e-6; if (std::abs (m_min_value - d.m_min_value) > epsilon) { return false; @@ -1424,6 +1433,8 @@ Object::from_string (const char *str, const char *base_dir) ex.read (h); } else if (ex.test ("is_visible=")) { ex.read (m_visible); + } else if (ex.test ("layer_binding=")) { + ex.read (m_layer_binding); } else if (ex.test ("z_position=")) { ex.read (m_z_position); } else if (ex.test ("min_value=")) { @@ -1814,6 +1825,12 @@ Object::to_string () const os << tl::to_string (m_z_position); os << ";"; + if (m_layer_binding != db::LayerProperties ()) { + os << "layer_binding="; + os << m_layer_binding.to_string (); + os << ";"; + } + os << "brightness="; os << tl::to_string (data_mapping ().brightness); os << ";"; @@ -1953,6 +1970,7 @@ Object::swap (Object &other) std::swap (mp_pixel_data, other.mp_pixel_data); m_landmarks.swap (other.m_landmarks); std::swap (m_z_position, other.m_z_position); + std::swap (m_layer_binding, other.m_layer_binding); std::swap (m_updates_enabled, other.m_updates_enabled); } diff --git a/src/img/img/imgObject.h b/src/img/img/imgObject.h index 214ea6c17..db1e719c8 100644 --- a/src/img/img/imgObject.h +++ b/src/img/img/imgObject.h @@ -31,6 +31,7 @@ #include "dbTrans.h" #include "dbMatrix.h" #include "dbPolygon.h" +#include "dbLayerProperties.h" #include "tlDataMapping.h" #include "tlColor.h" #include "tlPixelBuffer.h" @@ -924,6 +925,30 @@ public: return m_z_position; } + /** + * @brief Sets the layer binding + * + * If the image is bound to a layer, it becomes hidden when the layer is hidden + * and visible if the layer is visible too + */ + void set_layer_binding (const db::LayerProperties &lp) + { + if (m_layer_binding != lp) { + m_layer_binding = lp; + if (m_updates_enabled) { + property_changed (); + } + } + } + + /** + * @brief Gets the layer binding + */ + const db::LayerProperties &layer_binding () const + { + return m_layer_binding; + } + /** * @brief Get the RGB pixel data sets obtained by applying the LUT's */ @@ -1021,6 +1046,7 @@ private: mutable const tl::color_t *mp_pixel_data; std::vector m_landmarks; int m_z_position; + db::LayerProperties m_layer_binding; bool m_updates_enabled; void release (); diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index b5663d8aa..77da34a13 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -109,6 +109,8 @@ PropertiesPage::init () colors->setEnabled (false); value_le->setEnabled (false); + attach_service (mp_service); + connect (browse_pb, SIGNAL (clicked ()), this, SLOT (browse ())); connect (colors, SIGNAL (color_changed (std::pair)), false_color_control, SLOT (set_current_color (std::pair))); connect (false_color_control, SIGNAL (selection_changed (std::pair)), colors, SLOT (set_color (std::pair))); @@ -147,6 +149,20 @@ PropertiesPage::init () connect (define_landmarks_pb, SIGNAL (clicked ()), this, SLOT (define_landmarks_pressed ())); } +void +PropertiesPage::attach_service (img::Service *service) +{ + layer_binding_cbx->set_new_layer_enabled (false); + layer_binding_cbx->set_no_layer_available (true); + + if (service && service->view ()) { + int cv_index = service->view ()->active_cellview_index (); + layer_binding_cbx->set_view (service->view (), cv_index, true); + } else { + layer_binding_cbx->set_view (0, -1); + } +} + void PropertiesPage::invalidate () { @@ -195,6 +211,10 @@ PropertiesPage::description () const void PropertiesPage::confine_selection (const std::vector &remaining_entries) { + if (! mp_service) { + return; + } + std::vector org_selection; m_selection.swap (org_selection); for (auto i = remaining_entries.begin (); i != remaining_entries.end (); ++i) { @@ -459,6 +479,8 @@ PropertiesPage::update () to_le->setText (tl::to_qstring (tl::to_string (mp_direct_image->max_value ()))); to_le->setCursorPosition (0); + layer_binding_cbx->set_current_layer (mp_direct_image->layer_binding ()); + false_color_control->set_nodes (mp_direct_image->data_mapping ().false_color_nodes); brightness_slider->setValue (int (floor (mp_direct_image->data_mapping ().brightness * 100 + 0.5))); @@ -908,6 +930,8 @@ PropertiesPage::apply (bool /*commit*/) mp_direct_image->set_min_value (xmin); mp_direct_image->set_max_value (xmax); + mp_direct_image->set_layer_binding (layer_binding_cbx->current_layer_props ()); + img::DataMapping dm (mp_direct_image->data_mapping ()); dm.brightness = brightness_sb->value () * 0.01; dm.contrast = contrast_sb->value () * 0.01; diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index 32f453a1e..dbfd3525b 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -61,6 +61,7 @@ public: virtual void apply (bool commit); void set_direct_image (img::Object *image); + void attach_service (img::Service *service); private slots: void browse (); diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index 5f23b5892..fc45ffe99 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -58,10 +58,11 @@ class AddNewImageDialog public Ui::AddNewImageDialog { public: - AddNewImageDialog (QWidget *parent, img::Object *image_object) + AddNewImageDialog (QWidget *parent, img::Service *service, img::Object *image_object) : QDialog (parent), mp_image_object (image_object) { setupUi (this); + properties_frame->attach_service (service); properties_frame->set_direct_image (image_object); properties_frame->update (); } @@ -290,7 +291,7 @@ void View::render (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas) { const img::Object *image = image_object (); - if (! image) { + if (! image || ! image->is_visible ()) { return; } @@ -419,12 +420,16 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view) m_move_mode (Service::move_none), m_moved_landmark (0), m_keep_selection_for_move (false), - m_images_visible (true) + m_images_visible (true), + m_visibility_cache_valid (false) { // place images behind the grid z_order (-1); mp_view->annotations_changed_event.add (this, &Service::annotations_changed); + mp_view->layer_list_changed_event.add (this, &Service::layer_list_changed); + mp_view->active_cellview_changed_event.add (this, &Service::layer_visibilty_changed); + mp_view->current_layer_list_changed_event.add (this, &Service::current_layer_list_changed); } Service::~Service () @@ -436,9 +441,20 @@ Service::~Service () clear_transient_selection (); } +void +Service::layer_visibilty_changed () +{ + if (m_visibility_cache_valid && ! m_visibility_cache.empty ()) { + view ()->redraw_deco_layer (); + } + m_visibility_cache_valid = false; +} + void Service::annotations_changed () { + m_visibility_cache_valid = false; + // NOTE: right now, we don't differentiate: every annotation change may be a change in an image too. // We just forward this event as a potential image changed event images_changed_event (); @@ -449,7 +465,7 @@ Service::show_images (bool f) { if (m_images_visible != f) { m_images_visible = f; - view ()->redraw (); + view ()->redraw_deco_layer (); } } @@ -918,6 +934,61 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type) m_move_mode = move_none; } +bool +Service::image_is_visible (const img::Object *image) +{ + if (! image->is_visible ()) { + return false; + } + + if (! m_visibility_cache_valid) { + + std::vector images_with_bindings; + + for (obj_iterator user_object = mp_view->annotation_shapes ().begin (); user_object != mp_view->annotation_shapes ().end (); ++user_object) { + const img::Object *i = dynamic_cast ((*user_object).ptr ()); + if (i && ! i->layer_binding ().is_null ()) { + images_with_bindings.push_back (i); + } + } + + m_visibility_cache.clear (); + + if (! images_with_bindings.empty ()) { + + for (auto img = images_with_bindings.begin (); img != images_with_bindings.end (); ++img) { + m_visibility_cache.insert (std::make_pair (*img, true)); + } + + int cv_index = view ()->active_cellview_index (); + if (cv_index < 0) { + cv_index = 0; + } + + const lay::LayerPropertiesList &lp = view ()->get_properties (); + for (auto i = lp.begin_const_recursive (); ! i.at_end (); ++i) { + if (! i->has_children ()) { + const lay::ParsedLayerSource &source = i->source (true); + if (source.cv_index () == cv_index) { + for (auto img = images_with_bindings.begin (); img != images_with_bindings.end (); ++img) { + if (source.layer_props ().log_equal ((*img)->layer_binding ())) { + m_visibility_cache [*img] = i->visible (true); + } + } + } + } + } + + } + + m_visibility_cache_valid = true; + + } + + auto i = m_visibility_cache.find (image); + return i != m_visibility_cache.end () ? i->second : true; +} + const db::DUserObject * Service::find_image (const db::DPoint &p, const db::DBox &search_box, double l, double &dmin, const std::set *exclude) { @@ -931,7 +1002,7 @@ Service::find_image (const db::DPoint &p, const db::DBox &search_box, double l, lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_box); while (! r.at_end ()) { const img::Object *image = dynamic_cast ((*r).ptr ()); - if (image && image->is_visible () && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) { + if (image && image_is_visible (image) && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) { images.push_back (&*r); } ++r; @@ -1317,7 +1388,7 @@ Service::select (const db::DBox &box, lay::Editable::SelectionMode mode) lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox); while (! r.at_end ()) { const img::Object *iobj = dynamic_cast ((*r).ptr ()); - if (iobj && iobj->is_visible () && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) { + if (iobj && image_is_visible (iobj) && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) { if (is_selected (*iobj, box)) { any_selected = true; if (select (mp_view->annotation_shapes ().iterator_from_pointer (&*r), mode)) { @@ -1463,7 +1534,7 @@ Service::render_bg (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas) lay::AnnotationShapes::touching_iterator user_object = mp_view->annotation_shapes ().begin_touching (vp.box ()); while (! user_object.at_end ()) { const img::Object *image = dynamic_cast ((*user_object).ptr ()); - if (image && image->is_visible ()) { + if (image && image_is_visible (image)) { images.push_back (image); } ++user_object; @@ -1606,7 +1677,7 @@ Service::add_image () #if defined(HAVE_QT) img::Object *new_image = new img::Object (); - AddNewImageDialog dialog (QApplication::activeWindow (), new_image); + AddNewImageDialog dialog (QApplication::activeWindow (), this, new_image); if (dialog.exec ()) { clear_selection (); diff --git a/src/img/img/imgService.h b/src/img/img/imgService.h index 266761f1f..9e5d1cc14 100644 --- a/src/img/img/imgService.h +++ b/src/img/img/imgService.h @@ -457,6 +457,11 @@ public: return m_images_visible; } + /** + * @brief Returns a value indicating whether the given image is visible + */ + bool image_is_visible (const img::Object *image); + /** * @brief Implement the menu response function */ @@ -513,6 +518,9 @@ private: bool m_keep_selection_for_move; // Flag indicating whether images are visible bool m_images_visible; + // The visibility cache for the layer bindings + bool m_visibility_cache_valid; + std::map m_visibility_cache; void show_message (); @@ -578,6 +586,27 @@ private: * @brief Event handler for changes in the annotations */ void annotations_changed (); + + /** + * @brief Event handler for changes in the layer visibility + */ + void layer_list_changed (int) + { + layer_visibilty_changed (); + } + + /** + * @brief Event handler for a change of the current layer list + */ + void current_layer_list_changed (int) + { + layer_visibilty_changed (); + } + + /** + * @brief Common handler for a potential change in layer visibility + */ + void layer_visibilty_changed (); }; } diff --git a/testdata/ruby/imgObject.rb b/testdata/ruby/imgObject.rb index 38d9b85e7..c7a716eb6 100644 --- a/testdata/ruby/imgObject.rb +++ b/testdata/ruby/imgObject.rb @@ -212,6 +212,14 @@ class IMG_TestClass < TestBase image = copy1 assert_equal(image.to_s, "color:matrix=(0,-2.5,-2.75) (2.5,0,7.5) (0,0,1);min_value=0;max_value=1;is_visible=true;z_position=0;brightness=0;contrast=0;gamma=1;red_gain=1;green_gain=1;blue_gain=1;color_mapping=[0,'#000000';1,'#ffffff';];width=2;height=3;data=[0,1,0;0.5,1.5,0;1.5,2.5,0;2.5,3.5,0;10,0,0;20,0,0;]") + assert_equal(image.layer_binding.to_s, "") + image.layer_binding = RBA::LayerInfo::new(17, 42) + assert_equal(image.layer_binding.to_s, "17/42") + assert_equal(image.to_s, "color:matrix=(0,-2.5,-2.75) (2.5,0,7.5) (0,0,1);min_value=0;max_value=1;is_visible=true;z_position=0;layer_binding=17/42;brightness=0;contrast=0;gamma=1;red_gain=1;green_gain=1;blue_gain=1;color_mapping=[0,'#000000';1,'#ffffff';];width=2;height=3;data=[0,1,0;0.5,1.5,0;1.5,2.5,0;2.5,3.5,0;10,0,0;20,0,0;]") + assert_equal(RBA::Image::from_s(image.to_s).to_s, image.to_s) + image.layer_binding = RBA::LayerInfo::new + assert_equal(image.layer_binding.to_s, "") + dm = image.data_mapping.dup dm.brightness=0.5 image.data_mapping = dm