diff --git a/src/img/img/ImagePropertiesPage.ui b/src/img/img/ImagePropertiesPage.ui index 1c6ee4d0a..d0453605b 100644 --- a/src/img/img/ImagePropertiesPage.ui +++ b/src/img/img/ImagePropertiesPage.ui @@ -1,7 +1,8 @@ - + + ImagePropertiesPage - - + + 0 0 @@ -9,42 +10,65 @@ 642 - + Form - - - 9 - - + + 6 + + 9 + + + 9 + + + 9 + + + 9 + - - + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - - - - - 5 - 0 + + + + Browse + + + + + + + 0 0 - + Sans Serif 12 @@ -55,275 +79,187 @@ false - + Image Properties - - - - - 5 - 5 - 1 - 0 - - - - QFrame::Box - - - QFrame::Plain - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - - 5 - 5 - 1 - 0 - - - - <Filename> - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - - - - Browse - - - - - - - <File info> - - - - - - - QFrame::Plain - - - File - - - - - - + + + QFrame::NoFrame - + QFrame::Plain - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - - - + + + h = - - + + - - - + + + x = - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - - - + + + Perspective - - + + - - + + - - - + + + degree - - + + - - + + - - - + + + Shear angle - - - + + + Pixel Size - - - + + + degree - - - + + + micron - - - + + + Rotation angle - - - + + + micron - - - + + + w = - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + micron - - - + + + Center - - - + + + x = - - - + + + Mirrored (at X-axis) - - - + + + micron - - + + - - - + + + degree - + - + Qt::Vertical - + 20 5 @@ -331,29 +267,29 @@ - - - + + + y = - - - + + + degree - + - + Qt::Vertical - + QSizePolicy::Fixed - + 507 16 @@ -361,18 +297,18 @@ - - + + - + - + Qt::Vertical - + QSizePolicy::Fixed - + 507 5 @@ -380,30 +316,30 @@ - - - + + + y = - - - + + + Landmarks - - - + + + Define - - - + + + (use them to align the image with the layout) @@ -411,65 +347,185 @@ + + + + QFrame::Plain + + + File + + + + + + + + 1 + 0 + + + + QFrame::Box + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + 1 + 0 + + + + <Filename> + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + + + Save As ... + + + + + + + <File info> + + + - - + + Data To Display - - - 9 - - + + 6 + + 9 + + + 9 + + + 9 + + + 9 + - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + + + 0 + + + 0 + + + 0 + - - + + Pixel value range - + - - + + ... to - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - + Qt::Horizontal - + 40 20 @@ -481,64 +537,97 @@ - - - 2 + + + 0 - - + + Color Mapping - - - 9 - - + + 6 + + 9 + + + 9 + + + 9 + + + 9 + - - - - 13 - 7 + + + 0 0 - + Qt::ActionsContextMenu - - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal; text-decoration:none;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Double-click to create a new node.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Press "Delete" to delete the selected node.</p></body></html> +</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Double-click to create a new node.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Press "Delete" to delete the selected node.</p></body></html> - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + + + 0 + + + 0 + + + 0 + + + + + Value + + + + + + + + 0 + 0 + + + + - + Qt::Horizontal - + 21 20 @@ -547,35 +636,19 @@ p, li { white-space: pre-wrap; } - - - Value - - - - - - - - 5 - 0 - 0 - 0 - - - - - - - + + Color - - - + + + QFrame::NoFrame + + + QFrame::Raised @@ -584,53 +657,62 @@ p, li { white-space: pre-wrap; } - - + + Brightness / Contrast - - + + 9 - + + 9 + + + 9 + + + 9 + + 6 - - - - 100 - - + + + -100 - + + 100 + + 1 - - - + + + 100% - - - + + + 3.0 - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -638,119 +720,119 @@ p, li { white-space: pre-wrap; } - - - + + + 100 - + Qt::Horizontal - - - + + + 100% - - - + + + 0.3 - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + -100 - + 100 - + Qt::Horizontal - - - + + + -100 - + 100 - + Qt::Horizontal - - - + + + Contrast - - - - 100 - - + + + -100 + + 100 + - - - + + + -100% - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Gamma - - - + + + -100% - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Brightness - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -758,65 +840,74 @@ p, li { white-space: pre-wrap; } - - - - 3.000000000000000 - - + + + 0.000000000000000 - + + 3.000000000000000 + + 0.010000000000000 - - + + RGB Channels - - + + 9 - + + 9 + + + 9 + + + 9 + + 6 - - - + + + 2.000000000000000 - + 0.010000000000000 - - - + + + 0 - - - + + + x 2.0 - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -824,114 +915,114 @@ p, li { white-space: pre-wrap; } - - - + + + 100 - + Qt::Horizontal - - - + + + Green - - - + + + 0 - - - + + + 0 - - - + + + 100 - + Qt::Horizontal - - - + + + 100 - + Qt::Horizontal - - - + + + Blue - - - + + + Red - - - + + + x 2.0 - - - + + + 2.000000000000000 - + 0.010000000000000 - - - + + + 2.000000000000000 - + 0.010000000000000 - - - + + + x 2.0 - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -944,36 +1035,45 @@ p, li { white-space: pre-wrap; } - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + + + 0 + + + 0 + + + 0 + - - + + Preview (Auto apply) - + true - + Qt::Horizontal - + 40 20 @@ -982,8 +1082,8 @@ p, li { white-space: pre-wrap; } - - + + Reset @@ -996,10 +1096,10 @@ p, li { white-space: pre-wrap; } - + Qt::Vertical - + 524 16 @@ -1010,16 +1110,17 @@ p, li { white-space: pre-wrap; } - - lay::SimpleColorButton - QPushButton -
layWidgets.h
-
img::ColorBar QWidget
imgWidgets.h
+ + img::TwoColorWidget + QFrame +
imgWidgets.h
+ 1 +
width_le @@ -1048,7 +1149,6 @@ p, li { white-space: pre-wrap; } brightness_sb value_le browse_pb - color_pb gamma_slider brightness_slider diff --git a/src/img/img/gsiDeclImg.cc b/src/img/img/gsiDeclImg.cc index e72ee4a0c..1fbcc7ac5 100644 --- a/src/img/img/gsiDeclImg.cc +++ b/src/img/img/gsiDeclImg.cc @@ -26,6 +26,7 @@ #include "gsiSignals.h" #include "imgObject.h" #include "imgService.h" +#include "imgStream.h" #include "dbTilingProcessor.h" #include "layLayoutView.h" @@ -44,7 +45,12 @@ static void clear_colormap (img::DataMapping *dm) static void add_colormap (img::DataMapping *dm, double value, lay::color_t color) { - dm->false_color_nodes.push_back (std::make_pair (value, QColor (color))); + dm->false_color_nodes.push_back (std::make_pair (value, std::make_pair (QColor (color), QColor (color)))); +} + +static void add_colormap2 (img::DataMapping *dm, double value, lay::color_t lcolor, lay::color_t rcolor) +{ + dm->false_color_nodes.push_back (std::make_pair (value, std::make_pair (QColor (lcolor), QColor (rcolor)))); } static size_t num_colormap_entries (const img::DataMapping *dm) @@ -55,7 +61,25 @@ static size_t num_colormap_entries (const img::DataMapping *dm) static lay::color_t colormap_color (const img::DataMapping *dm, size_t i) { if (i < dm->false_color_nodes.size ()) { - return dm->false_color_nodes [i].second.rgb (); + return dm->false_color_nodes [i].second.first.rgb (); + } else { + return 0; + } +} + +static lay::color_t colormap_lcolor (const img::DataMapping *dm, size_t i) +{ + if (i < dm->false_color_nodes.size ()) { + return dm->false_color_nodes [i].second.first.rgb (); + } else { + return 0; + } +} + +static lay::color_t colormap_rcolor (const img::DataMapping *dm, size_t i) +{ + if (i < dm->false_color_nodes.size ()) { + return dm->false_color_nodes [i].second.second.rgb (); } else { return 0; } @@ -147,20 +171,53 @@ gsi::Class decl_ImageDataMapping ("lay", "ImageDataMapping", "blue component (0 to 255), the second byte the green component and the third byte the " "red component, i.e. 0xff0000 is red and 0x0000ff is blue. " ) + + gsi::method_ext ("add_colormap_entry", &gsi::add_colormap2, gsi::arg ("value"), gsi::arg ("lcolor"), gsi::arg ("rcolor"), + "@brief Add a colormap entry for this data mapping object.\n" + "@param value The value at which the given color should be applied.\n" + "@param lcolor The color to apply left of the value (a 32 bit RGB value).\n" + "@param rcolor The color to apply right of the value (a 32 bit RGB value).\n" + "\n" + "This settings establishes a color mapping for a given value in the monochrome channel. " + "The colors must be given as a 32 bit integer, where the lowest order byte describes the " + "blue component (0 to 255), the second byte the green component and the third byte the " + "red component, i.e. 0xff0000 is red and 0x0000ff is blue.\n" + "\n" + "In contrast to the version with one color, this version allows specifying a color left and right " + "of the value - i.e. a discontinuous step.\n" + "\n" + "This variant has been introduced in version 0.27.\n" + ) + gsi::method_ext ("num_colormap_entries", &gsi::num_colormap_entries, "@brief Returns the current number of color map entries.\n" "@return The number of entries.\n" ) + - gsi::method_ext ("colormap_color", &gsi::colormap_color, gsi::arg ("n"), - "@brief Returns the color for a given color map entry.\n" - "@param n The index of the entry (0..\\num_colormap_entries-1)\n" - "@return The color (see \\add_colormap_entry for a description).\n" - ) + gsi::method_ext ("colormap_value", &gsi::colormap_value, gsi::arg ("n"), "@brief Returns the vlue for a given color map entry.\n" "@param n The index of the entry (0..\\num_colormap_entries-1)\n" "@return The value (see \\add_colormap_entry for a description).\n" ) + + gsi::method_ext ("colormap_color", &gsi::colormap_color, gsi::arg ("n"), + "@brief Returns the color for a given color map entry.\n" + "@param n The index of the entry (0..\\num_colormap_entries-1)\n" + "@return The color (see \\add_colormap_entry for a description).\n" + "\n" + "NOTE: this version is deprecated and provided for backward compatibility. For discontinuous nodes " + "this method delivers the left-sided color." + ) + + gsi::method_ext ("colormap_lcolor", &gsi::colormap_lcolor, gsi::arg ("n"), + "@brief Returns the left-side color for a given color map entry.\n" + "@param n The index of the entry (0..\\num_colormap_entries-1)\n" + "@return The color (see \\add_colormap_entry for a description).\n" + "\n" + "This method has been introduced in version 0.27." + ) + + gsi::method_ext ("colormap_rcolor", &gsi::colormap_rcolor, gsi::arg ("n"), + "@brief Returns the right-side color for a given color map entry.\n" + "@param n The index of the entry (0..\\num_colormap_entries-1)\n" + "@return The color (see \\add_colormap_entry for a description).\n" + "\n" + "This method has been introduced in version 0.27." + ) + gsi::method_ext ("brightness=", &gsi::set_brightness, gsi::arg ("brightness"), "@brief Set the brightness\n" "See \\brightness for a description of this property.\n" @@ -350,6 +407,31 @@ private: tl::DeferredMethod dm_update_view; }; +static ImageRef *img_from_s (const std::string &s) +{ + std::auto_ptr img (new ImageRef ()); + img->from_string (s.c_str ()); + return img.release (); +} + +static ImageRef *load_image (const std::string &path) +{ + tl::InputFile file (path); + tl::InputStream stream (file); + + std::auto_ptr read; + read.reset (img::ImageStreamer::read (stream)); + // need to create a copy for now ... + return new ImageRef (*read); +} + +static void save_image (const ImageRef *image, const std::string &path) +{ + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, *image); +} + static ImageRef *new_image () { return new ImageRef (); @@ -474,6 +556,20 @@ static std::vector get_mask_data (ImageRef *obj) gsi::Class decl_BasicImage ("lay", "BasicImage", gsi::Methods (), "@hide"); gsi::Class decl_Image (decl_BasicImage, "lay", "Image", + gsi::constructor ("from_s", &gsi::img_from_s, gsi::arg ("s"), + "@brief Creates an image from the string returned by \\to_s.\n" + "This method has been introduced in version 0.27." + ) + + gsi::constructor ("read", &load_image, gsi::arg ("path"), + "@brief Loads the image from the given path.\n" + "\n" + "This method expects the image file as a KLayout image format file (.lyimg). " + "This is a XML-based format containing the image data plus placement and transformation " + "information for the image placement. In addition, image manipulation parameters for " + "false color display and color channel enhancement are embedded.\n" + "\n" + "This method has been introduced in version 0.27." + ) + gsi::constructor ("new", &gsi::new_image, "@brief Create a new image with the default attributes" "\n" @@ -614,6 +710,10 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", "@param t The magnifying transformation to apply\n" "@return The transformed object\n" ) + + gsi::method ("clear", &ImageRef::clear, + "@brief Clears the image data (sets to 0 or black).\n" + "This method has been introduced in version 0.27." + ) + gsi::method ("width", &ImageRef::width, "@brief Gets the width of the image in pixels\n" "@return The width in pixels\n" @@ -922,7 +1022,12 @@ gsi::Class decl_Image (decl_BasicImage, "lay", "Image", ) + gsi::method ("to_s", &ImageRef::to_string, "@brief Converts the image to a string\n" + "The string returned can be used to create an image object using \\from_s.\n" "@return The string\n" + ) + + gsi::method_ext ("write", &save_image, gsi::arg ("path"), + "@brief Saves the image to KLayout's image format (.lyimg)\n" + "This method has been introduced in version 0.27." ), "@brief An image to be stored as a layout annotation\n" "\n" @@ -1276,7 +1381,7 @@ public: { if (mp_image) { db::Matrix3d m = db::Matrix3d::disp ((p0 - db::DPoint ()) + db::DVector (nx * dx * 0.5, ny * dy * 0.5)) * db::Matrix3d::mag (dx, dy); - *mp_image = img::Object (nx, ny, m, false); + *mp_image = img::Object (nx, ny, m, false, false); } } diff --git a/src/img/img/img.pro b/src/img/img/img.pro index 2f667acbb..c5337e156 100644 --- a/src/img/img/img.pro +++ b/src/img/img/img.pro @@ -15,7 +15,8 @@ HEADERS = \ imgService.h \ imgWidgets.h \ imgForceLink.h \ - imgCommon.h + imgCommon.h \ + imgStream.h FORMS = \ AddNewImageDialog.ui \ @@ -31,7 +32,8 @@ SOURCES = \ imgPropertiesPage.cc \ imgService.cc \ imgWidgets.cc \ - imgForceLink.cc + imgForceLink.cc \ + imgStream.cc INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC $$LAYBASIC_INC $$DB_INC DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC $$LAYBASIC_INC $$DB_INC diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index 1e0e45cd8..c0da813d9 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -23,6 +23,7 @@ #include "imgObject.h" #include "imgWidgets.h" // for interpolate_color() +#include "imgStream.h" #include "tlLog.h" #include "tlTimer.h" #include "layPlugin.h" @@ -50,8 +51,8 @@ namespace img DataMapping::DataMapping () : brightness (0.0), contrast (0.0), gamma (1.0), red_gain (1.0), green_gain (1.0), blue_gain (1.0) { - false_color_nodes.push_back (std::make_pair (0.0, QColor (0, 0, 0))); - false_color_nodes.push_back (std::make_pair (1.0, QColor (255, 255, 255))); + false_color_nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); + false_color_nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); } bool @@ -91,7 +92,10 @@ DataMapping::operator== (const DataMapping &d) const if (fabs (false_color_nodes[i].first - d.false_color_nodes[i].first) > epsilon) { return false; } - if (false_color_nodes[i].second != d.false_color_nodes[i].second) { + if (false_color_nodes[i].second.first != d.false_color_nodes[i].second.first) { + return false; + } + if (false_color_nodes[i].second.second != d.false_color_nodes[i].second.second) { return false; } } @@ -136,8 +140,11 @@ DataMapping::operator< (const DataMapping &d) const if (fabs (false_color_nodes[i].first - d.false_color_nodes[i].first) > epsilon) { return false_color_nodes[i].first < d.false_color_nodes[i].first; } - if (false_color_nodes[i].second != d.false_color_nodes[i].second) { - return false_color_nodes[i].second.rgb () < d.false_color_nodes[i].second.rgb (); + if (false_color_nodes[i].second.first != d.false_color_nodes[i].second.first) { + return false_color_nodes[i].second.first.rgb () < d.false_color_nodes[i].second.first.rgb (); + } + if (false_color_nodes[i].second.second != d.false_color_nodes[i].second.second) { + return false_color_nodes[i].second.second.rgb () < d.false_color_nodes[i].second.second.rgb (); } } @@ -186,10 +193,10 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns for (unsigned int i = 1; i < false_color_nodes.size (); ++i) { int h1, s1, v1; - false_color_nodes [i - 1].second.getHsv (&h1, &s1, &v1); + false_color_nodes [i - 1].second.second.getHsv (&h1, &s1, &v1); int h2, s2, v2; - false_color_nodes [i].second.getHsv (&h2, &s2, &v2); + false_color_nodes [i].second.first.getHsv (&h2, &s2, &v2); // The number of steps is chosen such that the full HSV band divides into approximately 200 steps double nsteps = 0.5 * sqrt (double (h1 - h2) * double (h1 - h2) + double (s1 - s2) * double (s1 - s2) + double (v1 - v2) * double (v1 - v2)); @@ -220,11 +227,11 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns double ylast = 0.0; if (channel == 0) { - ylast = false_color_nodes.back ().second.red (); + ylast = false_color_nodes.back ().second.second.red (); } else if (channel == 1) { - ylast = false_color_nodes.back ().second.green (); + ylast = false_color_nodes.back ().second.second.green (); } else if (channel == 2) { - ylast = false_color_nodes.back ().second.blue (); + ylast = false_color_nodes.back ().second.second.blue (); } gray_to_color->push_back (false_color_nodes.back ().first, ylast / 255.0); @@ -704,31 +711,16 @@ Object::Object () mp_pixel_data = 0; } -Object::Object (size_t w, size_t h, const db::DCplxTrans &trans, bool color) +Object::Object (size_t w, size_t h, const db::DCplxTrans &trans, bool color, bool byte_data) : m_trans (trans), m_id (make_id ()), m_min_value (0.0), m_max_value (1.0), m_min_value_set (false), m_max_value_set (false), m_visible (true), m_z_position (0) { m_updates_enabled = false; mp_pixel_data = 0; - mp_data = new DataHeader (w, h, color, false); + mp_data = new DataHeader (w, h, color, byte_data); mp_data->add_ref (); - - // The default data type is float - tl_assert (! is_byte_data ()); - - if (is_color ()) { - for (unsigned int c = 0; c < 3; ++c) { - float *d = mp_data->float_data (c); - for (size_t i = data_length (); i > 0; --i) { - *d++ = 0.0; - } - } - } else { - float *d = mp_data->float_data (); - for (size_t i = data_length (); i > 0; --i) { - *d++ = 0.0; - } - } + clear (); + m_updates_enabled = true; } Object::Object (size_t w, size_t h, const db::DCplxTrans &trans, unsigned char *d) @@ -802,32 +794,15 @@ Object::Object (const std::string &filename, const db::DCplxTrans &trans) m_updates_enabled = true; } -Object::Object (size_t w, size_t h, const db::Matrix3d &trans, bool color) +Object::Object (size_t w, size_t h, const db::Matrix3d &trans, bool color, bool byte_data) : m_trans (trans), m_id (make_id ()), m_min_value (0.0), m_max_value (1.0), m_min_value_set (false), m_max_value_set (false), m_visible (true), m_z_position (0) { m_updates_enabled = false; mp_pixel_data = 0; - mp_data = new DataHeader (w, h, color, false); + mp_data = new DataHeader (w, h, color, byte_data); mp_data->add_ref (); - - // The default data type is float - tl_assert (! is_byte_data ()); - - if (is_color ()) { - for (unsigned int c = 0; c < 3; ++c) { - float *d = mp_data->float_data (c); - for (size_t i = data_length (); i > 0; --i) { - *d++ = 0.0; - } - } - } else { - float *d = mp_data->float_data (); - for (size_t i = data_length (); i > 0; --i) { - *d++ = 0.0; - } - } - + clear (); m_updates_enabled = true; } @@ -1078,6 +1053,48 @@ Object::clone () const return new img::Object (*this); } +void +Object::clear () +{ + if (is_byte_data ()) { + + if (is_color ()) { + + for (unsigned int c = 0; c < 3; ++c) { + unsigned char *d = mp_data->byte_data (c); + for (size_t i = data_length (); i > 0; --i) { + *d++ = 0.0; + } + } + + } else { + + unsigned char *d = mp_data->byte_data (); + for (size_t i = data_length (); i > 0; --i) { + *d++ = 0.0; + } + + } + + } else if (is_color ()) { + + for (unsigned int c = 0; c < 3; ++c) { + float *d = mp_data->float_data (c); + for (size_t i = data_length (); i > 0; --i) { + *d++ = 0.0; + } + } + + } else { + + float *d = mp_data->float_data (); + for (size_t i = data_length (); i > 0; --i) { + *d++ = 0.0; + } + + } +} + db::DPolygon Object::image_box_poly (const db::DBox vp, const db::DCplxTrans &vpt) const { @@ -1266,18 +1283,33 @@ Object::from_string (const char *str, const char *base_dir) double x = 0.0; lay::ColorConverter cc; - QColor c; + QColor cl, cr; std::string s; m_data_mapping.false_color_nodes.clear (); while (! ex.at_end () && ! ex.test ("]")) { + ex.read (x); + ex.test (","); + + s.clear (); ex.read_word_or_quoted (s); - cc.from_string (s, c); - m_data_mapping.false_color_nodes.push_back (std::make_pair (x, c)); + cc.from_string (s, cl); + + if (ex.test (",")) { + s.clear (); + ex.read_word_or_quoted (s); + cc.from_string (s, cr); + } else { + cr = cl; + } + + m_data_mapping.false_color_nodes.push_back (std::make_pair (x, std::make_pair (cl, cr))); + ex.test (";"); + } } else if (ex.test ("width=")) { @@ -1456,6 +1488,24 @@ Object::read_file () tl::info << "Reading image file " << m_filename; } + try { + + tl::InputFile file (m_filename); + tl::InputStream stream (file); + std::auto_ptr read; + read.reset (img::ImageStreamer::read (stream)); + read->m_filename = m_filename; + + // for now we need to copy here ... + *this = *read; + + // exit on success + return; + + } catch (...) { + // continue with other formats ... + } + QImage qimage (tl::to_qstring (m_filename)); if (! qimage.isNull ()) { @@ -1604,7 +1654,12 @@ Object::to_string () const for (unsigned int i = 0; i < data_mapping ().false_color_nodes.size (); ++i) { os << data_mapping ().false_color_nodes[i].first; os << ","; - os << tl::to_word_or_quoted_string (cc.to_string (data_mapping ().false_color_nodes[i].second)); + const std::pair &clr = data_mapping ().false_color_nodes[i].second; + os << tl::to_word_or_quoted_string (cc.to_string (clr.first)); + if (clr.first != clr.second) { + os << ","; + os << tl::to_word_or_quoted_string (cc.to_string (clr.second)); + } os << ";"; } @@ -1681,6 +1736,25 @@ Object::to_string () const return os.str (); } +void +Object::swap (Object &other) +{ + m_filename.swap (other.m_filename); + std::swap (m_trans, other.m_trans); + std::swap (mp_data, other.mp_data); + std::swap (m_id, other.m_id); + std::swap (m_min_value, other.m_min_value); + std::swap (m_max_value, other.m_max_value); + std::swap (m_min_value_set, other.m_min_value_set); + std::swap (m_max_value_set, other.m_max_value_set); + std::swap (m_data_mapping, other.m_data_mapping); + std::swap (m_visible, other.m_visible); + 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_updates_enabled, other.m_updates_enabled); +} + size_t Object::width () const { diff --git a/src/img/img/imgObject.h b/src/img/img/imgObject.h index 82c9702ff..b68e5a040 100644 --- a/src/img/img/imgObject.h +++ b/src/img/img/imgObject.h @@ -52,6 +52,8 @@ class DataHeader; struct IMG_PUBLIC DataMapping { public: + typedef std::vector< std::pair > > false_color_nodes_type; + /** * @brief The constructor */ @@ -73,7 +75,7 @@ public: * Each node is a pair or x-value (normalized to a range of 0..1) and a corresponding color. * The list should have an element with x value of 0.0 and one with an x value of 1.0. */ - std::vector< std::pair > false_color_nodes; + false_color_nodes_type false_color_nodes; /** * @brief The brightness value @@ -177,8 +179,9 @@ public: * @param h The height of the image * @param trans The transformation from pixel space to micron space * @param color True to create a color image. + * @param byte_data True to make the image store the data in bytes */ - Object (size_t w, size_t h, const db::DCplxTrans &trans, bool color); + Object (size_t w, size_t h, const db::DCplxTrans &trans, bool color, bool byte_data); /** * @brief Constructor for a monochrome image with the given pixel values @@ -301,8 +304,9 @@ public: * @param h The height of the image * @param matrix The 3d transformation matrix from pixel space to micron space * @param color True to create a color image. + * @param byte_data True to create n image using bytes rather than floats */ - Object (size_t w, size_t h, const db::Matrix3d &matrix, bool color); + Object (size_t w, size_t h, const db::Matrix3d &matrix, bool color, bool byte_data); /** * @brief Constructor for a monochrome image with the given pixel values @@ -734,6 +738,11 @@ public: */ void set_data (size_t width, size_t height, const std::vector &red, const std::vector &green, const std::vector &blue); + /** + * @brief Clears the pixel data (sets the values to 0) + */ + void clear (); + /** * @brief Set the transformation matrix * @@ -937,6 +946,11 @@ public: */ virtual std::string to_string () const; + /** + * @brief Swap with another image object + */ + void swap (img::Object &other); + /** * @brief Return the memory used in bytes */ diff --git a/src/img/img/imgPlugin.cc b/src/img/img/imgPlugin.cc index fa88fe549..9df38ba76 100644 --- a/src/img/img/imgPlugin.cc +++ b/src/img/img/imgPlugin.cc @@ -29,6 +29,8 @@ namespace img { +const std::string cfg_images_visible ("images-visible"); + void PluginDeclaration::get_menu_entries (std::vector &menu_entries) const { @@ -39,6 +41,7 @@ PluginDeclaration::get_menu_entries (std::vector &menu_entries) menu_entries.push_back (lay::menu_item ("img::bring_to_front", "bring_to_front:edit", "edit_menu.image_menu.end", tl::to_string (QObject::tr ("Image Stack: Selected Images to Front")))); menu_entries.push_back (lay::menu_item ("img::bring_to_back", "bring_to_back:edit", "edit_menu.image_menu.end", tl::to_string (QObject::tr ("Image Stack: Selected Images to Back")))); menu_entries.push_back (lay::menu_item ("img::clear_all_images", "clear_all_images:edit", "edit_menu.image_menu.end", tl::to_string (QObject::tr ("Clear All Images")))); + menu_entries.push_back (lay::config_menu_item ("show_images", "view_menu.layout_group+", tl::to_string (QObject::tr ("Show Images")), cfg_images_visible, "?")); } lay::Plugin * @@ -54,6 +57,12 @@ PluginDeclaration::implements_editable (std::string &title) const return true; } +void +PluginDeclaration::get_options (std::vector < std::pair > &options) const +{ + options.push_back (std::pair (cfg_images_visible, "true")); +} + static tl::RegisteredClass config_decl (new img::PluginDeclaration (), 4000, "img::Plugin"); } diff --git a/src/img/img/imgPlugin.h b/src/img/img/imgPlugin.h index 5c1dcb632..bd392c7c8 100644 --- a/src/img/img/imgPlugin.h +++ b/src/img/img/imgPlugin.h @@ -29,18 +29,16 @@ namespace img { +extern const std::string cfg_images_visible; + class PluginDeclaration : public lay::PluginDeclaration { public: - virtual void get_options (std::vector < std::pair > & /*options*/) const - { - // .. nothing yet .. - } - virtual void get_menu_entries (std::vector &menu_entries) const; virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *, lay::LayoutView *view) const; virtual bool implements_editable (std::string &title) const; + virtual void get_options (std::vector < std::pair > &options) const; }; } diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index b18293bb2..f53c0a100 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -23,9 +23,11 @@ #include "imgPropertiesPage.h" #include "imgLandmarksDialog.h" +#include "imgStream.h" #include "layLayoutView.h" #include "layFileDialog.h" #include "tlExceptions.h" +#include "tlFileUtils.h" namespace img { @@ -100,11 +102,12 @@ PropertiesPage::init () connect (action, SIGNAL (triggered ()), this, SLOT (reverse_color_order ())); false_color_control->addAction (action); - color_pb->set_color (QColor ()); + colors->set_color (std::make_pair (QColor (), QColor ())); + colors->setEnabled (false); connect (browse_pb, SIGNAL (clicked ()), this, SLOT (browse ())); - connect (color_pb, SIGNAL (color_changed (QColor)), false_color_control, SLOT (set_current_color (QColor))); - connect (false_color_control, SIGNAL (selection_changed (QColor)), color_pb, SLOT (set_color (QColor))); + 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))); connect (brightness_slider, SIGNAL (valueChanged (int)), this, SLOT (brightness_slider_changed (int))); connect (brightness_sb, SIGNAL (valueChanged (int)), this, SLOT (brightness_spinbox_changed (int))); @@ -126,6 +129,7 @@ PropertiesPage::init () connect (value_le, SIGNAL (returnPressed ()), this, SLOT (value_return_pressed ())); connect (reset_pb, SIGNAL (clicked ()), this, SLOT (reset_pressed ())); + connect (save_pb, SIGNAL (clicked ()), this, SLOT (save_pressed ())); connect (preview_cbx, SIGNAL (clicked ()), this, SLOT (preview_checked ())); connect (define_landmarks_pb, SIGNAL (clicked ()), this, SLOT (define_landmarks_pressed ())); } @@ -206,7 +210,8 @@ BEGIN_PROTECTED value_le->setText (QString ()); value_le->setEnabled (false); - color_pb->setEnabled (false_color_control->has_selection ()); + colors->setEnabled (false_color_control->has_selection ()); + colors->set_single_mode (false); double xmin, xmax; tl::from_string (tl::to_string (from_le->text ()), xmin); @@ -223,6 +228,10 @@ BEGIN_PROTECTED value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx))); value_le->setEnabled (true); + } else if (false_color_control->has_selection ()) { + + colors->set_single_mode (true); + } recompute_histogram (); @@ -240,7 +249,8 @@ PropertiesPage::color_mapping_changed () value_le->setText (QString ()); value_le->setEnabled (false); - color_pb->setEnabled (false_color_control->has_selection ()); + colors->setEnabled (false_color_control->has_selection ()); + colors->set_single_mode (false); try { @@ -259,6 +269,10 @@ PropertiesPage::color_mapping_changed () value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx))); value_le->setEnabled (true); + } else if (false_color_control->has_selection ()) { + + colors->set_single_mode (true); + } } catch (...) { } @@ -668,27 +682,27 @@ PropertiesPage::blue_spinbox_changed (double value) void PropertiesPage::black_to_white () { - std::vector > nodes; - nodes.push_back (std::make_pair (0.0, QColor (0, 0, 0))); - nodes.push_back (std::make_pair (1.0, QColor (255, 255, 255))); + std::vector > > nodes; + nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); false_color_control->set_nodes (nodes); } void PropertiesPage::white_to_black () { - std::vector > nodes; - nodes.push_back (std::make_pair (0.0, QColor (255, 255, 255))); - nodes.push_back (std::make_pair (1.0, QColor (0, 0, 0))); + std::vector > > nodes; + nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); false_color_control->set_nodes (nodes); } void PropertiesPage::red_to_blue () { - std::vector > nodes; - nodes.push_back (std::make_pair (0.0, QColor (255, 0, 0))); - nodes.push_back (std::make_pair (1.0, QColor (0, 0, 255))); + std::vector > > nodes; + nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (255, 0, 0), QColor (255, 0, 0)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (0, 0, 255), QColor (0, 0, 255)))); false_color_control->set_nodes (nodes); } @@ -696,18 +710,19 @@ PropertiesPage::red_to_blue () void PropertiesPage::blue_to_red () { - std::vector > nodes; - nodes.push_back (std::make_pair (0.0, QColor (0, 0, 255))); - nodes.push_back (std::make_pair (1.0, QColor (255, 0, 0))); + std::vector > > nodes; + nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (0, 0, 255), QColor (0, 0, 255)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 0, 0), QColor (255, 0, 0)))); false_color_control->set_nodes (nodes); } void PropertiesPage::reverse_color_order () { - std::vector > nodes (false_color_control->nodes ()); + std::vector > > nodes (false_color_control->nodes ()); for (size_t i = 0; i < nodes.size () / 2; ++i) { - std::swap (nodes [i].second, nodes [nodes.size () - 1 - i].second); + std::swap (nodes [i].second.second, nodes [nodes.size () - 1 - i].second.first); + std::swap (nodes [i].second.first, nodes [nodes.size () - 1 - i].second.second); } false_color_control->set_nodes (nodes); } @@ -826,6 +841,31 @@ BEGIN_PROTECTED END_PROTECTED } +void +PropertiesPage::save_pressed () +{ +BEGIN_PROTECTED + + apply (); + + lay::FileDialog file_dialog (this, tl::to_string (QObject::tr ("Save As KLayout Image File")), tl::to_string (QObject::tr ("KLayout image files (*.lyimg);;All files (*)"))); + + std::string filename = mp_direct_image->filename (); + if (! filename.empty () && tl::extension (filename) != "lyimg") { + filename = tl::basename (filename) + ".lyimg"; + } + + if (file_dialog.get_save (filename)) { + + tl::OutputFile file (filename); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, *mp_direct_image); + + } + +END_PROTECTED +} + void PropertiesPage::reset_pressed () { diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index 6bbdc722d..1de6a3f88 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -86,6 +86,7 @@ private slots: void min_max_return_pressed (); void preview_checked (); void reset_pressed (); + void save_pressed (); void define_landmarks_pressed (); private: diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index 299982165..9df942dbe 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -34,8 +34,10 @@ #include "laybasicConfig.h" #include "layLayoutCanvas.h" #include "layProperties.h" +#include "layTipDialog.h" #include "tlExceptions.h" #include "imgService.h" +#include "imgPlugin.h" #include "ui_AddNewImageDialog.h" #include @@ -247,37 +249,6 @@ struct SortImagePtrByZOrder } }; -static const db::DUserObject *find_image (lay::LayoutView *view, const db::DPoint &p, const db::DBox &search_box, double l, double &dmin, const std::map *exclude = 0) -{ - std::vector images; - - // get valid images and sort by reverse z order (top one first) - lay::AnnotationShapes::touching_iterator r = 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 (view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) { - images.push_back (&*r); - } - ++r; - } - - std::stable_sort (images.begin (), images.end (), SortImagePtrByZOrder ()); - - // look for the "closest" image to the search box - dmin = std::numeric_limits ::max (); - const db::DUserObject *found = 0; - - for (std::vector ::const_iterator robj = images.begin (); robj != images.end (); ++robj) { - double d = std::numeric_limits ::max (); - if (is_selected (*dynamic_cast ((*robj)->ptr ()), p, view->box (), l, d)) { - found = *robj; - dmin = d; - } - } - - return found; -} - // ------------------------------------------------------------- View::View (img::Service *service, obj_iterator image_ref, img::View::Mode mode) @@ -440,7 +411,8 @@ Service::Service (db::Manager *manager, lay::LayoutView *view) mp_transient_view (0), m_move_mode (Service::move_none), m_moved_landmark (0), - m_keep_selection_for_landmark (false) + m_keep_selection_for_landmark (false), + m_images_visible (true) { // place images behind the grid z_order (-1); @@ -465,10 +437,29 @@ Service::annotations_changed () images_changed_event (); } -bool -Service::configure (const std::string & /*name*/, const std::string & /*value*/) +void +Service::show_images (bool f) { - return false; + if (m_images_visible != f) { + m_images_visible = f; + view ()->redraw (); + } +} + +bool +Service::configure (const std::string &name, const std::string &value) +{ + if (name == cfg_images_visible) { + + bool v = true; + tl::from_string (value, v); + show_images (v); + + return true; + + } else { + return false; + } } void @@ -649,7 +640,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang m_p1 = p; double dmin = std::numeric_limits ::max (); - const db::DUserObject *robj = find_image (mp_view, p, search_dbox, l, dmin); + const db::DUserObject *robj = find_image (p, search_dbox, l, dmin); if (robj) { const img::Object *iobj = dynamic_cast (robj->ptr ()); @@ -919,6 +910,42 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type) m_move_mode = move_none; } +const db::DUserObject * +Service::find_image (const db::DPoint &p, const db::DBox &search_box, double l, double &dmin, const std::map *exclude) +{ + if (! m_images_visible) { + return 0; + } + + std::vector images; + + // get valid images and sort by reverse z order (top one first) + 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 ())) { + images.push_back (&*r); + } + ++r; + } + + std::stable_sort (images.begin (), images.end (), SortImagePtrByZOrder ()); + + // look for the "closest" image to the search box + dmin = std::numeric_limits ::max (); + const db::DUserObject *found = 0; + + for (std::vector ::const_iterator robj = images.begin (); robj != images.end (); ++robj) { + double d = std::numeric_limits ::max (); + if (is_selected (*dynamic_cast ((*robj)->ptr ()), p, mp_view->box (), l, d)) { + found = *robj; + dmin = d; + } + } + + return found; +} + void Service::selection_to_view (img::View::Mode mode) { @@ -1134,7 +1161,7 @@ Service::click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mo // point selection: look for the "closest" images double dmin = std::numeric_limits ::max (); - const db::DUserObject *robj = find_image (mp_view, pos, search_dbox, l, dmin, exclude); + const db::DUserObject *robj = find_image (pos, search_dbox, l, dmin, exclude); // return the proximity value if (robj) { @@ -1157,7 +1184,7 @@ Service::transient_select (const db::DPoint &pos) // point selection: look for the "closest" image double dmin = std::numeric_limits ::max (); - const db::DUserObject *robj = find_image (mp_view, pos, search_dbox, l, dmin, &m_previous_selection); + const db::DUserObject *robj = find_image (pos, search_dbox, l, dmin, &m_previous_selection); // create the transient marker for the object found if (robj) { @@ -1200,6 +1227,10 @@ Service::clear_transient_selection () bool Service::select (const db::DBox &box, lay::Editable::SelectionMode mode) { + if (! m_images_visible) { + return false; + } + bool needs_update = false; bool any_selected = false; @@ -1274,7 +1305,7 @@ Service::select (const db::DBox &box, lay::Editable::SelectionMode mode) // point selection: look for the "closest" image double dmin = std::numeric_limits ::max (); - const db::DUserObject *robj = find_image (mp_view, box.p1 (), search_dbox, l, dmin, exclude); + const db::DUserObject *robj = find_image (box.p1 (), search_dbox, l, dmin, exclude); // select the one that was found if (robj) { @@ -1381,6 +1412,10 @@ Service::change_image_by_id (size_t id, const img::Object &to) void Service::render_bg (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas) { + if (! m_images_visible) { + return; + } + std::vector images; lay::AnnotationShapes::touching_iterator user_object = mp_view->annotation_shapes ().begin_touching (vp.box ()); @@ -1409,11 +1444,29 @@ void Service::menu_activated (const std::string &symbol) { if (symbol == "img::clear_all_images") { + manager ()->transaction (tl::to_string (QObject::tr ("Clear all images"))); clear_images (); manager ()->commit (); + } else if (symbol == "img::add_image") { + + if (! images_visible ()) { + lay::TipDialog td (QApplication::activeWindow (), + tl::to_string (QObject::tr ("Images are not visible. If you add an image you will not see it.\n\n" + "Choose 'View/Show Images' to make images visible.")), + "add-image-while-not-visible", + lay::TipDialog::okcancel_buttons); + lay::TipDialog::button_type button = lay::TipDialog::null_button; + td.exec_dialog (button); + if (button == lay::TipDialog::cancel_button) { + // Don't bother the user with more dialogs. + return; + } + } + add_image (); + } else if (symbol == "img::bring_to_back") { bring_to_back (); } else if (symbol == "img::bring_to_front") { diff --git a/src/img/img/imgService.h b/src/img/img/imgService.h index b4bf52ae6..b0073e551 100644 --- a/src/img/img/imgService.h +++ b/src/img/img/imgService.h @@ -421,6 +421,19 @@ public: { return mp_view; } + + /** + * @brief Shows or hides the images + */ + void show_images (bool f); + + /** + * @brief Returns a value indicating whether images are shown or hidden + */ + bool images_visible () const + { + return m_images_visible; + } /** * @brief Implement the menu response function @@ -477,6 +490,8 @@ private: size_t m_moved_landmark; // Flag indicating that we want to keep the selection after the landmark was moved bool m_keep_selection_for_landmark; + // Flag indicating whether images are visible + bool m_images_visible; void show_message (); @@ -506,6 +521,11 @@ private: */ void copy_selected (); + /** + * @brief Finds an image object from the given point + */ + const db::DUserObject *find_image (const db::DPoint &p, const db::DBox &search_box, double l, double &dmin, const std::map *exclude = 0); + /** * @brief Update m_selected_image_views to reflect the selection */ diff --git a/src/img/img/imgStream.cc b/src/img/img/imgStream.cc new file mode 100644 index 000000000..5b605881c --- /dev/null +++ b/src/img/img/imgStream.cc @@ -0,0 +1,470 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "imgStream.h" +#include "tlXMLParser.h" +#include "tlXMLWriter.h" +#include "tlTimer.h" +#include "layConverters.h" + +#include + +namespace img +{ + +class ImageProxy +{ +public: + ImageProxy (const img::Object *img = 0) + : mp_img (img), + m_width (1), m_height (1), + m_min_value (0.0), m_max_value (1.0), + m_color (false) + { + init (); + } + + bool is_color () const + { + return mp_img->is_color (); + } + + void set_color (bool f) + { + m_color = f; + } + + size_t width () const + { + return mp_img->width (); + } + + void set_width (size_t w) + { + m_width = w; + } + + size_t height () const + { + return mp_img->height (); + } + + void set_height (size_t h) + { + m_height = h; + } + + std::list::const_iterator begin_byte_data () const + { + return m_byte_data.begin (); + } + + std::list::const_iterator end_byte_data () const + { + return m_byte_data.end (); + } + + void push_byte_data (const std::string &s) + { + m_byte_data.push_back (s); + } + + std::list::const_iterator begin_data () const + { + return m_data.begin (); + } + + std::list::const_iterator end_data () const + { + return m_data.end (); + } + + void push_data (const std::string &s) + { + m_data.push_back (s); + } + + const db::Matrix3d &matrix () const + { + return mp_img->matrix (); + } + + void set_matrix (const db::Matrix3d &m) + { + m_matrix = m; + } + + double min_value () const + { + return mp_img->min_value (); + } + + void set_min_value (double h) + { + m_min_value = h; + } + + double max_value () const + { + return mp_img->max_value (); + } + + void set_max_value (double h) + { + m_max_value = h; + } + + const img::DataMapping &data_mapping () const + { + return mp_img->data_mapping (); + } + + void set_data_mapping (const img::DataMapping &dm) + { + m_data_mapping = dm; + } + + const img::Object::landmarks_type &landmarks () const + { + return mp_img->landmarks (); + } + + void set_landmarks (const img::Object::landmarks_type &lm) + { + m_landmarks = lm; + } + + img::Object *get_image () const; + +private: + const img::Object *mp_img; + + // reader mode + size_t m_width, m_height; + img::Object::landmarks_type m_landmarks; + img::DataMapping m_data_mapping; + double m_min_value, m_max_value; + db::Matrix3d m_matrix; + std::list m_byte_data, m_data; + bool m_color; + + void init (); +}; + +template +static void +string_to_pixels (img::Object *img, const std::string &s, size_t row, size_t w, bool color) +{ + tl::Extractor ex (s.c_str ()); + + size_t column = 0; + while (! ex.at_end () && column < w) { + + T1 r = 0; + T1 g = 0; + T1 b = 0; + T2 m = 0; + + unsigned int i = 0; + bool has_mask = false; + + while (! ex.at_end () && ! ex.test (";")) { + + if (i == 0) { + ex.read (r); + } else if (color && i == 1) { + ex.read (g); + } else if (color && i == 2) { + ex.read (b); + } else { + ex.read (m); + has_mask = true; + } + ++i; + + ex.test (","); + + } + + if (color) { + img->set_pixel (column, row, double (r), double (g), double (b)); + } else { + img->set_pixel (column, row, double (r)); + } + + if (has_mask) { + img->set_mask (column, row, m); + } + + ++column; + + } + +} + +img::Object * +ImageProxy::get_image () const +{ + std::auto_ptr img (new Object (std::max (size_t (1), m_width), std::max (size_t (1), m_height), m_matrix, m_color, ! m_byte_data.empty ())); + img->set_min_value (m_min_value); + img->set_max_value (m_max_value); + img->set_data_mapping (m_data_mapping); + img->set_landmarks (m_landmarks); + + if (! m_byte_data.empty ()) { + + std::list::const_iterator s = m_byte_data.begin (); + for (size_t i = 0; i < m_height; ++i) { + string_to_pixels (img.get (), *s++, i, m_width, m_color); + } + + } else { + + std::list::const_iterator s = m_data.begin (); + for (size_t i = 0; i < m_height; ++i) { + string_to_pixels (img.get (), *s++, i, m_width, m_color); + } + + } + + return img.release (); +} + +static void add_entry (std::string &heap, const float *&b, bool &first) +{ + if (b) { + if (! first) { + heap += ","; + } + heap += tl::to_string (*b++); + first = false; + } +} + +static void add_entry (std::string &heap, const unsigned char *&b, bool &first) +{ + if (b) { + if (! first) { + heap += ","; + } + heap += tl::to_string ((unsigned int) *b++); + first = false; + } +} + +template +static const std::string &data_to_string (std::string &heap, size_t l, const T1 *r, const T2 *g, const T3 *b, const T4 *m) +{ + heap.clear (); + + while (l-- > 0) { + bool first = true; + add_entry (heap, r, first); + add_entry (heap, g, first); + add_entry (heap, b, first); + add_entry (heap, m, first); + if (l > 0) { + heap += ";"; + } + } + + return heap; +} + +void +ImageProxy::init () +{ + if (!mp_img) { + return; + } + + size_t w = mp_img->width (); + size_t h = mp_img->height (); + + static std::string s; + + if (mp_img->is_color ()) { + + if (mp_img->is_byte_data ()) { + + const unsigned char *r = mp_img->byte_data (0); + const unsigned char *g = mp_img->byte_data (1); + const unsigned char *b = mp_img->byte_data (2); + const unsigned char *m = mp_img->mask (); + + for (size_t i = 0; i < h; ++i) { + m_byte_data.push_back (data_to_string (s, w, r + i * w, g + i * w, b + i * w, m ? (m + i * w) : 0)); + } + + } else { + + const float *r = mp_img->float_data (0); + const float *g = mp_img->float_data (1); + const float *b = mp_img->float_data (2); + const unsigned char *m = mp_img->mask (); + + for (size_t i = 0; i < h; ++i) { + m_data.push_back (data_to_string (s, w, r + i * w, g + i * w, b + i * w, m ? (m + i * w) : 0)); + } + + } + + } else { + + if (mp_img->is_byte_data ()) { + + const unsigned char *g = mp_img->byte_data (); + const unsigned char *m = mp_img->mask (); + + for (size_t i = 0; i < h; ++i) { + m_byte_data.push_back (data_to_string (s, w, g + i * w, (const unsigned char *) 0, (const unsigned char *) 0, m ? (m + i * w) : 0)); + } + + } else { + + const float *g = mp_img->float_data (); + const unsigned char *m = mp_img->mask (); + + for (size_t i = 0; i < h; ++i) { + m_data.push_back (data_to_string (s, w, g + i * w, (const float *) 0, (const float *) 0, m ? (m + i * w) : 0)); + } + + } + + } +} + +// -------------------------------------------------------------------------------------------------------------------------- + +namespace { + + struct PointConverter + { + std::string to_string (const db::DPoint &p) const + { + return p.to_string (); + } + + void from_string (const std::string &s, db::DPoint &p) const + { + tl::Extractor ex (s.c_str ()); + ex.read (p); + } + }; + + struct ColorMapConverter + { + std::string to_string (const std::pair > &cm) const + { + std::string s; + s = tl::to_string (cm.first); + s += ":"; + + lay::ColorConverter cc; + s += tl::to_word_or_quoted_string (cc.to_string (cm.second.first)); + if (cm.second.first != cm.second.second) { + s += ","; + s += tl::to_word_or_quoted_string (cc.to_string (cm.second.second)); + } + + return s; + } + + void from_string (const std::string &s, std::pair > &cm) const + { + tl::Extractor ex (s.c_str ()); + + ex.read (cm.first); + ex.test (":"); + + lay::ColorConverter cc; + + std::string w; + ex.read_word_or_quoted (w); + + cc.from_string (w, cm.second.first); + + if (ex.test (",")) { + + w.clear (); + ex.read_word_or_quoted (w); + + cc.from_string (w, cm.second.second); + + } else { + cm.second.second = cm.second.first; + } + } + }; + +} + +tl::XMLStruct s_img_structure ("image-data", + tl::make_member (&ImageProxy::is_color, &ImageProxy::set_color, "color") + + tl::make_member (&ImageProxy::width, &ImageProxy::set_width, "width") + + tl::make_member (&ImageProxy::height, &ImageProxy::set_height, "height") + + tl::make_member (&ImageProxy::matrix, &ImageProxy::set_matrix, "matrix") + + tl::make_member (&ImageProxy::min_value, &ImageProxy::set_min_value, "min-value") + + tl::make_member (&ImageProxy::max_value, &ImageProxy::set_max_value, "max-value") + + tl::make_element (&ImageProxy::data_mapping, &ImageProxy::set_data_mapping, "data-mapping", + tl::make_element (&img::DataMapping::false_color_nodes, "color-map", + tl::make_member (&img::DataMapping::false_color_nodes_type::begin, &img::DataMapping::false_color_nodes_type::end, &img::DataMapping::false_color_nodes_type::push_back, "color-map-entry", ColorMapConverter ()) + ) + + tl::make_member (&img::DataMapping::brightness, "brightness") + + tl::make_member (&img::DataMapping::contrast, "contrast") + + tl::make_member (&img::DataMapping::gamma, "gamma") + + tl::make_member (&img::DataMapping::red_gain, "red-gain") + + tl::make_member (&img::DataMapping::green_gain, "green-gain") + + tl::make_member (&img::DataMapping::blue_gain, "blue-gain") + ) + + tl::make_element (&ImageProxy::landmarks, &ImageProxy::set_landmarks, "landmarks", + tl::make_member (&img::Object::landmarks_type::begin, &img::Object::landmarks_type::end, &img::Object::landmarks_type::push_back, "landmark", PointConverter ()) + ) + + tl::make_member (&ImageProxy::begin_byte_data, &ImageProxy::end_byte_data, &ImageProxy::push_byte_data, "byte-data") + + tl::make_member (&ImageProxy::begin_data, &ImageProxy::end_data, &ImageProxy::push_data, "data") +); + +// -------------------------------------------------------------------------------------------------------------------------- + +img::Object * +ImageStreamer::read (tl::InputStream &stream) +{ + ImageProxy proxy; + + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading image file: ")) + stream.source ()); + tl::XMLStreamSource in (stream, tl::to_string (tr ("Image file"))); + s_img_structure.parse (in, proxy); + + return proxy.get_image (); +} + +void +ImageStreamer::write (tl::OutputStream &stream, const img::Object &img) +{ + ImageProxy proxy (&img); + + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Writing image file: ")) + stream.path ()); + s_img_structure.write (stream, proxy); +} + +} // namespace img + diff --git a/src/img/img/imgStream.h b/src/img/img/imgStream.h new file mode 100644 index 000000000..e6c5dfca2 --- /dev/null +++ b/src/img/img/imgStream.h @@ -0,0 +1,60 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_imgStream +#define HDR_imgStream + +#include "imgCommon.h" + +#include "imgObject.h" +#include "tlStream.h" + +namespace img { + +/** + * @brief An object streaming image data from or to files + */ +struct IMG_PUBLIC ImageStreamer +{ +public: + /** + * @brief The constructor + */ + ImageStreamer () { } + + /** + * @brief Reads an image Object from a stream + * + * This method returns a new'd object. It's the responsibility of the caller to delete the object. + */ + static Object *read(tl::InputStream &stream); + + /** + * @brief Writes an image object to a stream + */ + static void write (tl::OutputStream &stream, const img::Object &img); +}; + +} + +#endif + diff --git a/src/img/img/imgWidgets.cc b/src/img/img/imgWidgets.cc index 297db30a9..b50dd5496 100644 --- a/src/img/img/imgWidgets.cc +++ b/src/img/img/imgWidgets.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -45,36 +46,36 @@ const double epsilon = 1e-6; struct compare_first_of_node { - bool operator() (const std::pair &a, const std::pair &b) const + bool operator() (const std::pair > &a, const std::pair > &b) const { return a.first < b.first; } }; QColor -interpolated_color (const std::vector > &nodes, double x) +interpolated_color (const std::vector > > &nodes, double x) { if (nodes.size () < 1) { return QColor (); } else if (nodes.size () < 2) { - return nodes[0].second; + return x < nodes[0].first ? nodes[0].second.first : nodes[0].second.second; } else { - std::vector >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, QColor ()), compare_first_of_node ()); + std::vector > >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, std::make_pair (QColor (), QColor ())), compare_first_of_node ()); if (p == nodes.end ()) { - return nodes.back ().second; + return nodes.back ().second.second; } else if (p == nodes.begin ()) { - return nodes.front ().second; + return nodes.front ().second.first; } else { double x1 = p[-1].first; double x2 = p->first; int h1 = 0, s1 = 0, v1 = 0; - p[-1].second.getHsv (&h1, &s1, &v1); + p[-1].second.second.getHsv (&h1, &s1, &v1); int h2 = 0, s2 = 0, v2 = 0; - p->second.getHsv (&h2, &s2, &v2); + p->second.first.getHsv (&h2, &s2, &v2); int h = int (0.5 + h1 + double(x - x1) * double (h2 - h1) / double(x2 - x1)); int s = int (0.5 + s1 + double(x - x1) * double (s2 - s1) / double(x2 - x1)); @@ -89,11 +90,91 @@ interpolated_color (const std::vector > &nodes, doubl } } +// -------------------------------------------------------------------------------------------------------------------- + +TwoColorWidget::TwoColorWidget (QWidget *parent) + : QFrame (parent) +{ + setLayout (new QHBoxLayout (this)); + + mp_left = new lay::SimpleColorButton (this); + layout ()->addWidget (mp_left); + mp_right = new lay::SimpleColorButton (this); + layout ()->addWidget (mp_right); + mp_lock = new QToolButton (this); + layout ()->addWidget (mp_lock); + mp_lock->setCheckable (true); + mp_lock->setAutoRaise (true); + mp_lock->setIconSize (QSize (16, 16)); + + QIcon icon; + icon.addFile (":/locked_16.png", QSize (), QIcon::Normal, QIcon::On); + icon.addFile (":/unlocked_16.png", QSize (), QIcon::Normal, QIcon::Off); + mp_lock->setIcon (icon); + + connect (mp_left, SIGNAL (color_changed (QColor)), this, SLOT (lcolor_changed (QColor))); + connect (mp_right, SIGNAL (color_changed (QColor)), this, SLOT (rcolor_changed (QColor))); + connect (mp_lock, SIGNAL (clicked (bool)), this, SLOT (lock_changed (bool))); +} + +void +TwoColorWidget::set_color (std::pair c) +{ + mp_left->set_color (c.first); + mp_right->set_color (c.second); + mp_lock->setChecked (c.first == c.second); + mp_right->setVisible (! mp_lock->isChecked ()); +} + +void +TwoColorWidget::set_single_mode (bool f) +{ + mp_lock->setEnabled (! f); +} + +void +TwoColorWidget::lcolor_changed (QColor) +{ + if (mp_lock->isChecked ()) { + mp_right->set_color (mp_left->get_color ()); + } + emit color_changed (std::make_pair (mp_left->get_color (), mp_right->get_color ())); +} + +void +TwoColorWidget::rcolor_changed (QColor) +{ + if (mp_lock->isChecked ()) { + mp_left->set_color (mp_right->get_color ()); + } + emit color_changed (std::make_pair (mp_left->get_color (), mp_right->get_color ())); +} + +void +TwoColorWidget::lock_changed (bool checked) +{ + if (checked) { + + QColor cl = mp_left->get_color (); + QColor cr = mp_right->get_color (); + + QColor ca ((cl.red () + cr.red ()) / 2, (cl.green () + cr.green ()) / 2, (cl.blue () + cr.blue ()) / 2); + set_color (std::make_pair (ca, ca)); + + emit color_changed (std::make_pair (mp_left->get_color (), mp_right->get_color ())); + + } + + mp_right->setVisible (! mp_lock->isChecked ()); +} + +// -------------------------------------------------------------------------------------------------------------------- + ColorBar::ColorBar (QWidget *parent) : QWidget (parent), m_dragging (false), m_selected (-1) { - m_nodes.push_back (std::make_pair (0.0, QColor (0, 0, 0))); - m_nodes.push_back (std::make_pair (1.0, QColor (255, 255, 255))); + m_nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); + m_nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); } ColorBar::~ColorBar () @@ -122,7 +203,7 @@ ColorBar::mouseMoveEvent (QMouseEvent *event) } void -ColorBar::set_current_color (QColor c) +ColorBar::set_current_color (std::pair c) { if (has_selection ()) { m_nodes [m_selected].second = c; @@ -174,27 +255,27 @@ ColorBar::keyPressEvent (QKeyEvent *event) m_nodes.erase (m_nodes.begin () + m_selected); m_selected = -1; emit selection_changed (); - emit selection_changed (QColor ()); + emit selection_changed (std::make_pair (QColor (), QColor ())); update (); } } void -ColorBar::set_nodes (const std::vector > &nodes) +ColorBar::set_nodes (const std::vector > > &nodes) { m_nodes = nodes; std::sort (m_nodes.begin (), m_nodes.end (), compare_first_of_node ()); if (m_nodes.size () == 0 || fabs (m_nodes[0].first) > epsilon) { - m_nodes.insert (m_nodes.begin (), std::make_pair (0.0, QColor (0, 0, 0))); + m_nodes.insert (m_nodes.begin (), std::make_pair (0.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); } else { m_nodes[0].first = 0.0; } - std::vector >::iterator w = m_nodes.begin (); - std::vector >::const_iterator nn = m_nodes.begin (); - for (std::vector >::const_iterator n = m_nodes.begin () + 1; n != m_nodes.end (); ++n) { + std::vector > >::iterator w = m_nodes.begin (); + std::vector > >::const_iterator nn = m_nodes.begin (); + for (std::vector > >::const_iterator n = m_nodes.begin () + 1; n != m_nodes.end (); ++n) { if (fabs (nn->first - n->first) > min_value_interval) { *w++ = *nn; nn = n; @@ -207,7 +288,7 @@ ColorBar::set_nodes (const std::vector > &nodes) if (m_nodes.back ().first > 1.0 - min_value_interval) { m_nodes.back ().first = 1.0; } else { - m_nodes.push_back (std::make_pair (1.0, QColor (255, 255, 255))); + m_nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); } m_selected = -1; @@ -232,8 +313,8 @@ ColorBar::mousePressEvent (QMouseEvent *event) double xx = double (event->x () - xl) / double (xr - xl); double dmin = 100.0; - std::vector >::const_iterator pmin = m_nodes.end (); - for (std::vector >::const_iterator p = m_nodes.begin (); p != m_nodes.end (); ++p) { + std::vector > >::const_iterator pmin = m_nodes.end (); + for (std::vector > >::const_iterator p = m_nodes.begin (); p != m_nodes.end (); ++p) { double d = fabs (p->first - xx); if (d < 0.05 && d < dmin) { dmin = d; @@ -242,7 +323,7 @@ ColorBar::mousePressEvent (QMouseEvent *event) } if (pmin != m_nodes.end ()) { - m_selected = int (std::distance (std::vector >::const_iterator (m_nodes.begin ()), pmin)); + m_selected = int (std::distance (std::vector > >::const_iterator (m_nodes.begin ()), pmin)); emit selection_changed (); emit selection_changed (m_nodes [m_selected].second); m_dragging = true; @@ -250,7 +331,7 @@ ColorBar::mousePressEvent (QMouseEvent *event) } else { m_selected = -1; emit selection_changed (); - emit selection_changed (QColor ()); + emit selection_changed (std::make_pair (QColor (), QColor ())); update (); } @@ -278,10 +359,11 @@ ColorBar::mouseDoubleClickEvent (QMouseEvent *event) double xx = double (event->x () - xl) / double (xr - xl); - std::vector >::iterator p = std::lower_bound (m_nodes.begin (), m_nodes.end (), std::make_pair (xx, QColor ()), compare_first_of_node ()); + std::vector > >::iterator p = std::lower_bound (m_nodes.begin (), m_nodes.end (), std::make_pair (xx, std::make_pair (QColor (), QColor ())), compare_first_of_node ()); if (p != m_nodes.begin () && p != m_nodes.end ()) { m_selected = int (std::distance (m_nodes.begin (), p)); - m_nodes.insert (p, std::make_pair (xx, interpolated_color (m_nodes, xx))); + QColor ci = interpolated_color (m_nodes, xx); + m_nodes.insert (p, std::make_pair (xx, std::make_pair (ci, ci))); emit selection_changed (); emit selection_changed (m_nodes [m_selected].second); emit color_mapping_changed (); diff --git a/src/img/img/imgWidgets.h b/src/img/img/imgWidgets.h index cbcd6ba6f..1870fcb06 100644 --- a/src/img/img/imgWidgets.h +++ b/src/img/img/imgWidgets.h @@ -20,13 +20,15 @@ */ - - #ifndef HDR_imgWidgets #define HDR_imgWidgets +#include "layWidgets.h" + #include #include +#include +#include #include class QMouseEvent; @@ -41,7 +43,37 @@ namespace img * * TODO: move this somewhere else. */ -QColor interpolated_color (const std::vector > &nodes, double x); +QColor interpolated_color (const std::vector > > &nodes, double x); + +/** + * @brief A two-color widget + * + * This widget has two color buttons and a "lock" checkbox which makes both colors identical + */ +class TwoColorWidget + : public QFrame +{ +Q_OBJECT + +public: + TwoColorWidget (QWidget *parent); + +signals: + void color_changed (std::pair c); + +public slots: + void set_color (std::pair c); + void set_single_mode (bool f); + +private slots: + void lcolor_changed (QColor c); + void rcolor_changed (QColor c); + void lock_changed (bool checked); + +private: + lay::SimpleColorButton *mp_left, *mp_right; + QToolButton *mp_lock; +}; /** * @brief A color bar widget @@ -82,9 +114,9 @@ public: return m_selected >= 0; } - void set_nodes (const std::vector > &nodes); + void set_nodes (const std::vector > > &nodes); - const std::vector > &nodes () const + const std::vector > > &nodes () const { return m_nodes; } @@ -92,18 +124,18 @@ public: void set_histogram (const std::vector &histogram); public slots: - void set_current_color (QColor c); + void set_current_color (std::pair c); void set_current_position (double x); signals: void color_mapping_changed (); void selection_changed (); - void selection_changed (QColor c); + void selection_changed (std::pair c); private: bool m_dragging; int m_selected; - std::vector > m_nodes; + std::vector > > m_nodes; std::vector m_histogram; }; diff --git a/src/img/unit_tests/imgFile.cc b/src/img/unit_tests/imgFile.cc new file mode 100644 index 000000000..40ff82126 --- /dev/null +++ b/src/img/unit_tests/imgFile.cc @@ -0,0 +1,292 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "imgStream.h" +#include "tlUnitTest.h" + +#include + +TEST(1_FloatMono) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), false, false); + + image.set_min_value (-0.25); + image.set_max_value (0.75); + + std::vector lm; + lm.push_back (db::DPoint (1, 2)); + lm.push_back (db::DPoint (-101, 102)); + image.set_landmarks (lm); + + img::DataMapping dm; + dm.blue_gain = 0.5; + dm.green_gain = 0.75; + dm.red_gain = 0.25; + dm.contrast = -0.5; + dm.gamma = 1.5; + dm.brightness = 1.25; + dm.false_color_nodes.clear (); + dm.false_color_nodes.push_back (std::make_pair (0.0, std::make_pair (QColor (0, 0, 0), QColor (0, 0, 0)))); + dm.false_color_nodes.push_back (std::make_pair (0.5, std::make_pair (QColor (255, 0, 0), QColor (0, 255, 0)))); + dm.false_color_nodes.push_back (std::make_pair (1.0, std::make_pair (QColor (255, 255, 255), QColor (255, 255, 255)))); + image.set_data_mapping (dm); + + image.set_pixel (0, 0, 0.25); + image.set_pixel (2, 5, 0.25); + image.set_pixel (7, 1, 0.125); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(2_FloatMonoWithMask) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), false, false); + + image.set_min_value (-0.25); + image.set_max_value (0.75); + + image.set_pixel (0, 0, 0.25); + image.set_pixel (2, 5, 0.25); + image.set_pixel (7, 1, 0.125); + + image.set_mask (1, 0, 1); + image.set_mask (1, 2, 1); + image.set_mask (1, 3, 0); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(3_ByteMono) +{ + img::Object image (12, 8, db::Matrix3d (db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42))), false, true); + + image.set_min_value (10); + image.set_max_value (240); + + image.set_pixel (0, 0, 50); + image.set_pixel (2, 5, 70); + image.set_pixel (7, 1, 120); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(4_ByteMonoWithMask) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), false, true); + + image.set_min_value (10); + image.set_max_value (240); + + image.set_pixel (0, 0, 50); + image.set_pixel (2, 5, 70); + image.set_pixel (7, 1, 120); + + image.set_mask (1, 0, 1); + image.set_mask (1, 2, 1); + image.set_mask (1, 3, 0); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(5_FloatColor) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), true, false); + + image.set_min_value (-0.25); + image.set_max_value (0.75); + + image.set_pixel (0, 0, 0.25, -0.25, -0.125); + image.set_pixel (2, 5, 0.25, 0.125, 0.625); + image.set_pixel (7, 1, 0.125, 0.25, 0.75); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(6_FloatColorWithMask) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), true, false); + + image.set_min_value (-0.25); + image.set_max_value (0.75); + + image.set_pixel (0, 0, 0.25, -0.25, -0.125); + image.set_pixel (2, 5, 0.25, 0.125, 0.625); + image.set_pixel (7, 1, 0.125, 0.25, 0.75); + + image.set_mask (1, 0, 1); + image.set_mask (1, 2, 1); + image.set_mask (1, 3, 0); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(7_ByteColor) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), true, true); + + image.set_min_value (10); + image.set_max_value (240); + + image.set_pixel (0, 0, 10, 20, 30); + image.set_pixel (2, 5, 11, 21, 31); + image.set_pixel (7, 1, 12, 22, 32); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} + +TEST(8_ByteColorWithMask) +{ + img::Object image (12, 8, db::DCplxTrans (1.5, 90.0, true, db::DVector (17, -42)), true, true); + + image.set_min_value (10); + image.set_max_value (240); + + image.set_pixel (0, 0, 10, 20, 30); + image.set_pixel (2, 5, 11, 21, 31); + image.set_pixel (7, 1, 12, 22, 32); + + image.set_mask (1, 0, 1); + image.set_mask (1, 2, 1); + image.set_mask (1, 3, 0); + + std::string path = tmp_file ("tmp.lyimg"); + { + tl::OutputFile file (path); + tl::OutputStream stream (file); + img::ImageStreamer::write (stream, image); + } + + std::auto_ptr read; + + { + tl::InputFile file (path); + tl::InputStream stream (file); + read.reset (img::ImageStreamer::read (stream)); + } + + EXPECT_EQ (image.to_string (), read->to_string ()); +} diff --git a/src/img/unit_tests/imgObject.cc b/src/img/unit_tests/imgObject.cc index bc8200d64..86f6f1b47 100644 --- a/src/img/unit_tests/imgObject.cc +++ b/src/img/unit_tests/imgObject.cc @@ -25,9 +25,16 @@ #include "imgObject.h" #include "tlUnitTest.h" +static img::Object from_s (const std::string &s) +{ + img::Object img; + img.from_string (s.c_str ()); + return img; +} + TEST(1) { - img::Object image (12, 8, db::DCplxTrans (), false); + img::Object image (12, 8, db::DCplxTrans (), false, false); EXPECT_EQ (image.is_color (), false); EXPECT_EQ (image.is_byte_data (), false); @@ -95,25 +102,42 @@ TEST(1) dm.green_gain = 0.75; dm.blue_gain = 2.5; QColor c (128, 255, 64); - dm.false_color_nodes.insert (dm.false_color_nodes.begin () + 1, std::make_pair (0.5, c)); + QColor c2 (64, 32, 192); + dm.false_color_nodes.insert (dm.false_color_nodes.begin () + 1, std::make_pair (0.5, std::make_pair (c, c))); image.set_data_mapping (dm); EXPECT_EQ (copy1.equals (&image), false); + EXPECT_EQ (from_s (image.to_string ()).equals (&image), true); + copy1 = image; + EXPECT_EQ (copy1.equals (&image), true); + dm.false_color_nodes.insert (dm.false_color_nodes.begin () + 1, std::make_pair (0.75, std::make_pair (c, c2))); + image.set_data_mapping (dm); + EXPECT_EQ (copy1.equals (&image), false); + EXPECT_EQ (from_s (image.to_string ()).equals (&image), true); copy1 = image; EXPECT_EQ (copy1.equals (&image), true); EXPECT_EQ (copy1.data_mapping ().brightness, 0.5); EXPECT_EQ (copy1.data_mapping ().red_gain, 1.25); - EXPECT_EQ (copy1.data_mapping ().false_color_nodes.size (), size_t (3)); + EXPECT_EQ (copy1.data_mapping ().false_color_nodes.size (), size_t (4)); img::Object copy2; copy2.from_string (image.to_string ().c_str ()); + EXPECT_EQ (copy2.equals (&image), true); EXPECT_EQ (copy2.data_mapping ().brightness, 0.5); EXPECT_EQ (tl::to_string (copy2.data_mapping ().red_gain), "1.25"); - EXPECT_EQ (copy2.data_mapping ().false_color_nodes.size (), size_t (3)); + EXPECT_EQ (copy2.data_mapping ().false_color_nodes.size (), size_t (4)); EXPECT_EQ (copy2.equals (&image), true); + img::Object copy3, empty; + copy3.swap (copy2); + EXPECT_EQ (copy3.equals (&image), true); + EXPECT_EQ (copy2.equals (&empty), true); + copy3.swap (copy2); + EXPECT_EQ (copy2.equals (&image), true); + EXPECT_EQ (copy3.equals (&empty), true); + EXPECT_EQ (image.to_string (), copy2.to_string ()); EXPECT_EQ (image.mask (1, 2), true); @@ -134,7 +158,7 @@ TEST(1) TEST(2) { for (unsigned int channel = 0; channel < 3; ++channel) { - img::Object image (12, 8, db::DCplxTrans (), true); + img::Object image (12, 8, db::DCplxTrans (), true, false); EXPECT_EQ (image.is_color (), true); @@ -203,7 +227,7 @@ TEST(2) dm.green_gain = 0.75; dm.blue_gain = 2.5; QColor c (128, 255, 64); - dm.false_color_nodes.insert (dm.false_color_nodes.begin () + 1, std::make_pair (0.5, c)); + dm.false_color_nodes.insert (dm.false_color_nodes.begin () + 1, std::make_pair (0.5, std::make_pair (c, c))); image.set_data_mapping (dm); EXPECT_EQ (copy1.equals (&image), false); diff --git a/src/img/unit_tests/unit_tests.pro b/src/img/unit_tests/unit_tests.pro index fec02cec1..63793df87 100644 --- a/src/img/unit_tests/unit_tests.pro +++ b/src/img/unit_tests/unit_tests.pro @@ -8,6 +8,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ imgObject.cc \ + imgFile.cc INCLUDEPATH += $$IMG_INC $$DB_INC $$TL_INC $$LAYBASIC_INC $$GSI_INC DEPENDPATH += $$IMG_INC $$DB_INC $$TL_INC $$LAYBASIC_INC $$GSI_INC diff --git a/src/lay/lay/images/locked_16.png b/src/lay/lay/images/locked_16.png new file mode 100644 index 000000000..7d29dc559 Binary files /dev/null and b/src/lay/lay/images/locked_16.png differ diff --git a/src/lay/lay/images/unlocked_16.png b/src/lay/lay/images/unlocked_16.png new file mode 100644 index 000000000..6b5b0d538 Binary files /dev/null and b/src/lay/lay/images/unlocked_16.png differ diff --git a/src/lay/lay/layResources.qrc b/src/lay/lay/layResources.qrc index abaeec830..799f3f325 100644 --- a/src/lay/lay/layResources.qrc +++ b/src/lay/lay/layResources.qrc @@ -127,6 +127,8 @@ images/folder_12.png images/file_12.png images/empty_12.png + images/unlocked_16.png + images/locked_16.png syntax/ruby.xml diff --git a/src/laybasic/laybasic/layAbstractMenu.cc b/src/laybasic/laybasic/layAbstractMenu.cc index ba9a44f01..df59ab6ce 100644 --- a/src/laybasic/laybasic/layAbstractMenu.cc +++ b/src/laybasic/laybasic/layAbstractMenu.cc @@ -1468,6 +1468,9 @@ AbstractMenu::find_item (tl::Extractor &extr) if (n.empty ()) { // skip (avoids infinite loops on wrong paths) + while (! extr.at_end () && *extr != ';') { + ++extr; + } } else if (n == "begin") { diff --git a/src/tl/tl/tlString.cc b/src/tl/tl/tlString.cc index c57ccca0e..458b50808 100644 --- a/src/tl/tl/tlString.cc +++ b/src/tl/tl/tlString.cc @@ -940,6 +940,15 @@ Extractor::read (unsigned int &value) return *this; } +Extractor & +Extractor::read (unsigned char &value) +{ + if (! try_read (value)) { + error (tl::to_string (tr ("Expected an unsigned byte value"))); + } + return *this; +} + Extractor & Extractor::read (unsigned long &value) { @@ -967,6 +976,15 @@ Extractor::read (double &value) return *this; } +Extractor & +Extractor::read (float &value) +{ + if (! try_read (value)) { + error (tl::to_string (tr ("Expected a real number"))); + } + return *this; +} + Extractor & Extractor::read (int &value) { @@ -1099,6 +1117,14 @@ namespace return tl::to_string (tr ("Range overflow on unsigned integer")); } }; + + template <> struct overflow_msg_func + { + std::string operator() () const + { + return tl::to_string (tr ("Range overflow on unsigned byte")); + } + }; } template bool @@ -1167,6 +1193,12 @@ Extractor::try_read_unsigned_int (T &value) return true; } +bool +Extractor::try_read (unsigned char &value) +{ + return try_read_unsigned_int (value); +} + bool Extractor::try_read (unsigned int &value) { @@ -1202,6 +1234,19 @@ Extractor::try_read (long long &value) { return try_read_signed_int (value); } + +bool +Extractor::try_read (float &value) +{ + double d = value; + if (try_read (d)) { + value = d; + return true; + } else { + return false; + } +} + bool Extractor::try_read (double &value) { diff --git a/src/tl/tl/tlString.h b/src/tl/tl/tlString.h index 521c1b353..0bdbf2792 100644 --- a/src/tl/tl/tlString.h +++ b/src/tl/tl/tlString.h @@ -468,6 +468,11 @@ public: */ Extractor &read (unsigned int &value); + /** + * @brief Read an unsigned char int (see read of an unsigned int) + */ + Extractor &read (unsigned char &value); + /** * @brief Read an unsigned long (see read of an unsigned int) */ @@ -483,6 +488,11 @@ public: */ Extractor &read (double &value); + /** + * @brief Read a float (see read of an unsigned int) + */ + Extractor &read (float &value); + /** * @brief Read a signed int (see read of an unsigned int) */ @@ -576,6 +586,11 @@ public: */ bool try_read (int &value); + /** + * @brief Try to read an unsigned char int (see try to read an unsigned int) + */ + bool try_read (unsigned char &value); + /** * @brief Try to read an unsigned long (see try to read an unsigned int) */ @@ -601,6 +616,11 @@ public: */ bool try_read (double &value); + /** + * @brief Try to read a float (see try to read an unsigned int) + */ + bool try_read (float &value); + /** * @brief Try to read a boolean value (see try to read an unsigned int) * diff --git a/testdata/img/gs.png b/testdata/img/gs.png new file mode 100644 index 000000000..be41b0436 Binary files /dev/null and b/testdata/img/gs.png differ diff --git a/testdata/ruby/imgObject.rb b/testdata/ruby/imgObject.rb index 1f35a1688..7c1af0ae8 100644 --- a/testdata/ruby/imgObject.rb +++ b/testdata/ruby/imgObject.rb @@ -53,14 +53,31 @@ class IMG_TestClass < TestBase assert_equal(dm.num_colormap_entries, 0) dm.add_colormap_entry(0, 0x123456); dm.add_colormap_entry(0.5, 0xff0000); + dm.add_colormap_entry(0.75, 0x123456, 0x654321); dm.add_colormap_entry(1.0, 0x00ff00); - assert_equal(dm.num_colormap_entries, 3) + assert_equal(dm.num_colormap_entries, 4) assert_equal(dm.colormap_color(0) & 0xffffff, 0x123456) assert_equal(dm.colormap_value(0), 0.0) assert_equal(dm.colormap_color(1) & 0xffffff, 0xff0000) + assert_equal(dm.colormap_lcolor(1) & 0xffffff, 0xff0000) + assert_equal(dm.colormap_rcolor(1) & 0xffffff, 0xff0000) assert_equal(dm.colormap_value(1), 0.5) - assert_equal(dm.colormap_color(2) & 0xffffff, 0x00ff00) - assert_equal(dm.colormap_value(2), 1.0) + assert_equal(dm.colormap_color(2) & 0xffffff, 0x123456) + assert_equal(dm.colormap_lcolor(2) & 0xffffff, 0x123456) + assert_equal(dm.colormap_rcolor(2) & 0xffffff, 0x654321) + assert_equal(dm.colormap_value(2), 0.75) + assert_equal(dm.colormap_color(3) & 0xffffff, 0x00ff00) + assert_equal(dm.colormap_value(3), 1.0) + + image = RBA::Image.new + image.data_mapping = dm + + dm2 = image.data_mapping + assert_equal(dm2.num_colormap_entries, 4) + + dm.clear_colormap + assert_equal(dm2.num_colormap_entries, 4) + assert_equal(dm.num_colormap_entries, 0) end @@ -74,12 +91,21 @@ class IMG_TestClass < TestBase assert_equal(image.to_s, "empty:") assert_equal(image.is_empty?, true) + image2 = RBA::Image::from_s(image.to_s) + assert_equal(image2.to_s, image.to_s) + image = RBA::Image.new(2, 3, []) assert_equal(image.to_s, "mono:matrix=(1,0,0) (0,1,0) (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;0;0;0;0;0;]") + image2 = RBA::Image::from_s(image.to_s) + assert_equal(image2.to_s, image.to_s) + image = RBA::Image.new(2, 3, [], [], []) assert_equal(image.to_s, "color:matrix=(1,0,0) (0,1,0) (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,0,0;0,0,0;0,0,0;0,0,0;0,0,0;0,0,0;]") + image2 = RBA::Image::from_s(image.to_s) + assert_equal(image2.to_s, image.to_s) + data = [0.0, 0.5, 1.5, 2.5, 10, 20] image = RBA::Image.new(2, 3, data) assert_equal(image.to_s, "mono:matrix=(1,0,0) (0,1,0) (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;0.5;1.5;2.5;10;20;]") @@ -164,18 +190,18 @@ class IMG_TestClass < TestBase assert_equal(image.filename, "") - if false - image = RBA::Image.new("/home/matthias/a.png") - assert_equal(image.trans.to_s, "r0 *1 0,0") - assert_equal(image.width, 728) - assert_equal(image.height, 762) - assert_equal(image.filename, "/home/matthias/a.png") - - image = RBA::Image.new("/home/matthias/a.png", t) - assert_equal(image.trans.to_s, "r90 *2.5 1,5") - assert_equal(image.width, 728) - assert_equal(image.height, 762) - end + fn = ENV["TESTSRC"] + "/testdata/img/gs.png" + + image = RBA::Image.new(fn) + assert_equal(image.trans.to_s, "r0 *1 -513.5,-349") + assert_equal(image.width, 1027) + assert_equal(image.height, 698) + assert_equal(image.filename, fn) + + image = RBA::Image.new(fn, t) + assert_equal(image.trans.to_s, "r90 *1 873.5,-1278.75") + assert_equal(image.width, 1027) + assert_equal(image.height, 698) image.min_value = -12.5 assert_equal(image.min_value, -12.5) @@ -370,6 +396,25 @@ class IMG_TestClass < TestBase end + def test_4 + + tmp = File::join($ut_testtmp, "tmp.lyimg") + + t = RBA::DCplxTrans.new(2.5, 90, false, RBA::DPoint.new(1, 5)) + + image = RBA::Image.new(2, 3, t, [1,2,3,4,5,6]) + assert_equal(image.to_s, "mono: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=[1;2;3;4;5;6;]") + + image.write(tmp) + + image.clear + assert_equal(image.to_s, "mono: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;0;0;0;0;0;]") + + image2 = RBA::Image::read(tmp) + assert_equal(image2.to_s, "mono: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=[1;2;3;4;5;6;]") + + end + end load("test_epilogue.rb")