From f58daaa82b46a612a4c3b482598d442c6dadd364 Mon Sep 17 00:00:00 2001 From: mpv Date: Thu, 18 Jul 2013 07:37:31 +0000 Subject: [PATCH] HYDROOperations: initial development and tests --- CMakeLists.txt | 1 + src/HYDROData/HYDROData_Image.cxx | 93 ++++++++----- src/HYDROData/HYDROData_Image.h | 36 +++++ src/HYDROData/HYDROData_Object.cxx | 39 ++++++ src/HYDROData/HYDROData_Object.h | 17 +++ src/HYDROOperations/CMakeLists.txt | 53 ++++++++ src/HYDROOperations/HYDROOperations.h | 18 +++ .../HYDROOperations_BSpline.cxx | 58 +++++++++ src/HYDROOperations/HYDROOperations_BSpline.h | 36 +++++ .../HYDROOperations_Factory.cxx | 123 ++++++++++++++++++ src/HYDROOperations/HYDROOperations_Factory.h | 98 ++++++++++++++ .../test_HYDROOperations_BSpline.cxx | 64 +++++++++ .../test_HYDROOperations_BSpline.h | 26 ++++ .../test_HYDROOperations_Factory.cxx | 61 +++++++++ .../test_HYDROOperations_Factory.h | 25 ++++ .../test_HYDROOperations_Main.cxx | 54 ++++++++ 16 files changed, 768 insertions(+), 34 deletions(-) create mode 100644 src/HYDROOperations/CMakeLists.txt create mode 100644 src/HYDROOperations/HYDROOperations.h create mode 100644 src/HYDROOperations/HYDROOperations_BSpline.cxx create mode 100644 src/HYDROOperations/HYDROOperations_BSpline.h create mode 100644 src/HYDROOperations/HYDROOperations_Factory.cxx create mode 100644 src/HYDROOperations/HYDROOperations_Factory.h create mode 100644 src/HYDROOperations/test_HYDROOperations_BSpline.cxx create mode 100644 src/HYDROOperations/test_HYDROOperations_BSpline.h create mode 100644 src/HYDROOperations/test_HYDROOperations_Factory.cxx create mode 100644 src/HYDROOperations/test_HYDROOperations_Factory.h create mode 100644 src/HYDROOperations/test_HYDROOperations_Main.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index e50f8bb6..bcda5f39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,5 +22,6 @@ find_package(SalomeCAS) find_package(SalomeCPPUNIT) add_subdirectory (src/HYDROData) +add_subdirectory (src/HYDROOperations) enable_testing() diff --git a/src/HYDROData/HYDROData_Image.cxx b/src/HYDROData/HYDROData_Image.cxx index 62e1ba53..0d1da676 100644 --- a/src/HYDROData/HYDROData_Image.cxx +++ b/src/HYDROData/HYDROData_Image.cxx @@ -2,11 +2,17 @@ #include #include -#include #include +#include #include +#include +#include #include +// tage of the child of my label that contains information about the operator +static const int TAG_OPERATOR = 1; +static const Standard_GUID GUID_MUST_BE_UPDATED("80f2bb81-3873-4631-8ddd-940d2119f000"); + IMPLEMENT_STANDARD_HANDLE(HYDROData_Image, HYDROData_Object) IMPLEMENT_STANDARD_RTTIEXT(HYDROData_Image, HYDROData_Object) @@ -36,47 +42,21 @@ void HYDROData_Image::SetImage(const QImage& theImage) aParams->SetValue(3, theImage.bytesPerLine()); aParams->SetValue(4, (int)(theImage.format())); // store data of image in byte array - Handle(TDataStd_ByteArray) aData; - int aLen = theImage.byteCount(); - if (!myLab.FindAttribute(TDataStd_ByteArray::GetID(), aData)) { - aData = TDataStd_ByteArray::Set(myLab, 1, aLen); - } - // copy bytes one by one - const uchar* aBits = theImage.bits(); - if (aData->Length() != aLen) { - Handle(TColStd_HArray1OfByte) aNewData = new TColStd_HArray1OfByte(1, aLen); - for(int a = 0; a < aLen; a++) - aNewData->SetValue(a + 1, aBits[a]); - aData->ChangeArray(aNewData); - } else { - for(int a = 0; a < aLen; a++) - aData->SetValue(a + 1, aBits[a]); - } - + const char* aData = (const char*)(theImage.bits()); + SaveByteArray(0, aData, theImage.byteCount()); } QImage HYDROData_Image::Image() { Handle(TDataStd_IntegerArray) aParams; - Handle(TDataStd_ByteArray) aData; - if (!myLab.FindAttribute(TDataStd_IntegerArray::GetID(), aParams) || - !myLab.FindAttribute(TDataStd_ByteArray::GetID(), aData)) + if (!myLab.FindAttribute(TDataStd_IntegerArray::GetID(), aParams)) + return QImage(); // return empty image if there is no array + int aLen = 0; + uchar* anArray = (uchar*)ByteArray(0, aLen); + if (!aLen) return QImage(); // return empty image if there is no array - /* - // make uchar array one by one - int aLen = aData->Upper(); - uchar* anArray = new uchar[aLen]; - for(int a = 0; a < aLen; a++) - anArray[a] = aData->Value(a + 1); - // recreate image from integer parameters and array of bytes - QImage aResult(anArray, aParams->Value(1), aParams->Value(2), - aParams->Value(3), QImage::Format(aParams->Value(4))); - delete [] anArray; <- this is wrong, because QImage references to this array - */ - uchar* anArray = (uchar*)(void*)(&(aData->InternalArray()->ChangeArray1().ChangeValue(1))); QImage aResult(anArray, aParams->Value(1), aParams->Value(2), aParams->Value(3), QImage::Format(aParams->Value(4))); - return aResult; } @@ -184,3 +164,48 @@ void HYDROData_Image::ClearReferences() { myLab.ForgetAttribute(TDataStd_ReferenceList::GetID()); } + +void HYDROData_Image::SetOperatorName(const QString theOpName) +{ + TDataStd_Name::Set(myLab.FindChild(TAG_OPERATOR), + TCollection_ExtendedString(theOpName.toLatin1().constData())); +} + +QString HYDROData_Image::OperatorName() +{ + Handle(TDataStd_Name) aName; + if (myLab.FindChild(TAG_OPERATOR). + FindAttribute(TDataStd_Name::GetID(), aName)) { + TCollection_AsciiString aStr(aName->Get()); + return QString(aStr.ToCString()); + } + return QString(); +} + +void HYDROData_Image::SetArgs(const QByteArray& theArgs) +{ + SaveByteArray(TAG_OPERATOR, theArgs.constData(), theArgs.length()); +} + +QByteArray HYDROData_Image::Args() +{ + int aLen = 0; + const char* aData = ByteArray(TAG_OPERATOR, aLen); + if (!aLen) + return QByteArray(); + return QByteArray(aData, aLen); +} + +void HYDROData_Image::MustBeUpdated(bool theFlag) +{ + if (theFlag) { + TDataStd_UAttribute::Set(myLab, GUID_MUST_BE_UPDATED); + } else { + myLab.ForgetAttribute(GUID_MUST_BE_UPDATED); + } +} + +bool HYDROData_Image::MustBeUpdated() +{ + return myLab.IsAttribute(GUID_MUST_BE_UPDATED); +} diff --git a/src/HYDROData/HYDROData_Image.h b/src/HYDROData/HYDROData_Image.h index 38e48c19..167d8b54 100644 --- a/src/HYDROData/HYDROData_Image.h +++ b/src/HYDROData/HYDROData_Image.h @@ -85,6 +85,42 @@ public: */ HYDRODATA_EXPORT void ClearReferences(); + /** + * Stores the operator name + * \param theOpName name of the operator that must be executed for image update + */ + HYDRODATA_EXPORT void SetOperatorName(const QString theOpName); + + /** + * Returns the operator name + * \returns the name of the operator that must be executed for image update + */ + HYDRODATA_EXPORT QString OperatorName(); + + /** + * Stores the operator arguments + * \param theArgs array that stores the operator arguments, needed for execution + */ + HYDRODATA_EXPORT void SetArgs(const QByteArray& theArgs); + + /** + * Returns the operator arguments + * \returns array that stores the operator arguments, needed for execution + */ + HYDRODATA_EXPORT QByteArray Args(); + + /** + * Sets the "MustBeUpdated" flag: if image is depended on updated features. + * \param theFlag is true for images that must be updated, false for up-to-date + */ + HYDRODATA_EXPORT void MustBeUpdated(bool theFlag); + + /** + * Returns the "MustBeUpdated" flag: is image must be recomputed or not + * \returns false if image is up to date + */ + HYDRODATA_EXPORT bool MustBeUpdated(); + protected: friend class HYDROData_Iterator; diff --git a/src/HYDROData/HYDROData_Object.cxx b/src/HYDROData/HYDROData_Object.cxx index 488b03ef..46b4acc4 100644 --- a/src/HYDROData/HYDROData_Object.cxx +++ b/src/HYDROData/HYDROData_Object.cxx @@ -1,6 +1,7 @@ #include #include +#include #include IMPLEMENT_STANDARD_HANDLE(HYDROData_Object,MMgt_TShared) @@ -55,3 +56,41 @@ void HYDROData_Object::SetLabel(TDF_Label theLabel) { myLab = theLabel; } + +void HYDROData_Object::SaveByteArray(const int theTag, + const char* theData, const int theLen) +{ + TDF_Label aLab = theTag == 0 ? myLab : myLab.FindChild(theTag); + // array is empty, remove the attribute + if (theLen <= 0) { + aLab.ForgetAttribute(TDataStd_ByteArray::GetID()); + return; + } + // store data of image in byte array + Handle(TDataStd_ByteArray) aData; + if (!aLab.FindAttribute(TDataStd_ByteArray::GetID(), aData)) { + aData = TDataStd_ByteArray::Set(aLab, 1, theLen); + } + // copy bytes one by one + if (aData->Length() != theLen) { + Handle(TColStd_HArray1OfByte) aNewData = new TColStd_HArray1OfByte(1, theLen); + for(int a = 0; a < theLen; a++) + aNewData->SetValue(a + 1, theData[a]); + aData->ChangeArray(aNewData); + } else { + for(int a = 0; a < theLen; a++) + aData->SetValue(a + 1, theData[a]); + } +} + +const char* HYDROData_Object::ByteArray(const int theTag, int& theLen) +{ + TDF_Label aLab = theTag == 0 ? myLab : myLab.FindChild(theTag); + Handle(TDataStd_ByteArray) aData; + if (!aLab.FindAttribute(TDataStd_ByteArray::GetID(), aData)) + return NULL; // return empty image if there is no array + theLen = aData->Length(); + if (theLen) + return (const char*)(&(aData->InternalArray()->ChangeArray1().ChangeValue(1))); + return NULL; +} diff --git a/src/HYDROData/HYDROData_Object.h b/src/HYDROData/HYDROData_Object.h index 852c0679..763153a3 100644 --- a/src/HYDROData/HYDROData_Object.h +++ b/src/HYDROData/HYDROData_Object.h @@ -89,6 +89,23 @@ protected: * Returns the label of this object. */ TDF_Label& Label() {return myLab;} + + /** + * Internal method that used to store the byte array attribute + * \param theTag tag of a label to store attribute (for 0 this is myLab) + * \param theData pointer to bytes array + * \param theLen number of bytes in byte array that must be stored + */ + void SaveByteArray(const int theTag, const char* theData, const int theLen); + + /** + * Internal method that used to retreive the content of byte array attribute + * \param theTag tag of a label that keeps the attribute (for 0 this is myLab) + * \param theLen number of bytes in byte array + * \returns pointer to the internal data structure wit harray content, + * or NULL if array size is zero + */ + const char* ByteArray(const int theTag, int& theLen); protected: /// Array of pointers to the properties of this object; index in this array is returned by \a AddProperty. diff --git a/src/HYDROOperations/CMakeLists.txt b/src/HYDROOperations/CMakeLists.txt new file mode 100644 index 00000000..a6a8ac07 --- /dev/null +++ b/src/HYDROOperations/CMakeLists.txt @@ -0,0 +1,53 @@ +include(../../CMake/Common.cmake) + +set(PROJECT_HEADERS + HYDROOperations.h + HYDROOperations_Factory.h + HYDROOperations_BSpline.h +) + +set(PROJECT_SOURCES + HYDROOperations_Factory.cxx + HYDROOperations_BSpline.cxx +) + +add_definitions( + -DHYDROOPERATIONS_EXPORTS + ${CAS_DEFINITIONS} + ${QT_DEFINITIONS} + ${GUI_CXXFLAGS} +) + +include_directories( + ${CAS_INCLUDE_DIRS} + ${QT_INCLUDES} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../HYDROData +) + +add_library(HYDROOperations SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS}) +target_link_libraries(HYDROOperations ${CAS_MODELER} ${ImageComposer} HYDROData) + +set(PROJECT_LIBRARIES HYDROOperations) + +# tests +if(CPPUNIT_IS_OK) + + set(TEST_HEADERS + test_HYDROOperations_BSpline.h + test_HYDROOperations_Factory.h + ) + + set(TEST_SOURCES + test_HYDROOperations_Main.cxx + test_HYDROOperations_BSpline.cxx + test_HYDROOperations_Factory.cxx + ) + + set(TEST_EXE test_HYDROOperations) + include(../../CMake/CPPUnitTests.cmake) + target_link_libraries(test_HYDROOperations ${CAS_MODELER} ${QT_LIBRARIES} ${CPPUNIT_LIBS} HYDROOperations) + +endif(CPPUNIT_IS_OK) + +include(../../CMake/CommonInstall.cmake) diff --git a/src/HYDROOperations/HYDROOperations.h b/src/HYDROOperations/HYDROOperations.h new file mode 100644 index 00000000..feeaec5a --- /dev/null +++ b/src/HYDROOperations/HYDROOperations.h @@ -0,0 +1,18 @@ +#ifndef HYDROOPERATIONS_H +#define HYDROOPERATIONS_H + +#if defined HYDROOPERATIONS_EXPORTS +#if defined WIN32 +#define HYDROOPERATIONS_EXPORT __declspec( dllexport ) +#else +#define HYDROOPERATIONS_EXPORT +#endif +#else +#if defined WIN32 +#define HYDROOPERATIONS_EXPORT __declspec( dllimport ) +#else +#define HYDROOPERATIONS_EXPORT +#endif +#endif + +#endif diff --git a/src/HYDROOperations/HYDROOperations_BSpline.cxx b/src/HYDROOperations/HYDROOperations_BSpline.cxx new file mode 100644 index 00000000..376490da --- /dev/null +++ b/src/HYDROOperations/HYDROOperations_BSpline.cxx @@ -0,0 +1,58 @@ +#include + +#include +#include +#include +#include + +HYDROOperations_BSpline::HYDROOperations_BSpline( + QList& thePoints) +{ + // fill array for algorithm by the received coordinates + int aLen = thePoints.size() / 2; + Handle(TColgp_HArray1OfPnt) aHCurvePoints = new TColgp_HArray1OfPnt (1, aLen); + QList::iterator aListIter = thePoints.begin(); + for (int ind = 1; ind <= aLen; ind++) { + gp_Pnt aPnt(gp::Origin()); + aPnt.SetX(*aListIter); + aListIter++; + aPnt.SetY(*aListIter); + aListIter++; + aHCurvePoints->SetValue(ind, aPnt); + } + // compute BSpline + GeomAPI_Interpolate aGBC(aHCurvePoints, Standard_False, gp::Resolution()); + aGBC.Perform(); + if (aGBC.IsDone()) { + myCurve = aGBC.Curve(); + } +} + +QPainterPath HYDROOperations_BSpline::ComputePath() const +{ + QPainterPath aResult; + if (myCurve.IsNull()) // returns an empty Path if original curve is invalid + return aResult; + GeomConvert_BSplineCurveToBezierCurve aConverter(myCurve); + int a, aNumArcs = aConverter.NbArcs(); + for(a = 1; a <= aNumArcs; a++) { + Handle(Geom_BezierCurve) anArc = aConverter.Arc(a); + if (a == 1) { // set a start point + gp_Pnt aStart = anArc->StartPoint(); + aResult.moveTo(aStart.X(), aStart.Y()); + } + gp_Pnt anEnd = anArc->EndPoint(); + if (anArc->NbPoles() == 3) { // quadric segment in the path (pole 1 is start, pole 3 is end) + gp_Pnt aPole = anArc->Pole(2); + aResult.quadTo(aPole.X(), aPole.Y(), anEnd.X(), anEnd.Y()); + } else if (anArc->NbPoles() == 4) { // cubic segment (usually this is used) + gp_Pnt aPole1 = anArc->Pole(2); + gp_Pnt aPole2 = anArc->Pole(3); + aResult.cubicTo( + aPole1.X(), aPole1.Y(), aPole2.X(), aPole2.Y(), anEnd.X(), anEnd.Y()); + } else { // error, another number of poles is not supported + return QPainterPath(); + } + } + return aResult; +} diff --git a/src/HYDROOperations/HYDROOperations_BSpline.h b/src/HYDROOperations/HYDROOperations_BSpline.h new file mode 100644 index 00000000..20b9c859 --- /dev/null +++ b/src/HYDROOperations/HYDROOperations_BSpline.h @@ -0,0 +1,36 @@ +#ifndef HYDROOperations_BSpline_HeaderFile +#define HYDROOperations_BSpline_HeaderFile + +#include +#include +#include +#include + +/**\class HYDROOperations_BSpline + * + * \brief Allows to work with splines: create, convert to Qt ToolPath. + * + * Uses GEOM module for creation of BSplines, OCCT algorithms for + * manipulation and conversion. + */ + +class HYDROOperations_BSpline +{ +public: + + //! Creates a spline by list of coordinates: pairs X and Y + //! \param thePoints coordinates in format X1, Y1, X2, Y2, etc. must be even number of elements + HYDROOPERATIONS_EXPORT HYDROOperations_BSpline(QList& thePoints); + + //! Returns the BSpline curve passing through the points + //! \returns Null if Computation of BSpline was failed + Handle(Geom_BSplineCurve) Curve() const {return myCurve;} + + //! Performs conversion from BSpline curve to QPainterPath made from Bezier curves + //! \returns computed PainterPath, not stored in this class, so calling of this method is not fast + QPainterPath ComputePath() const; +private: + Handle(Geom_BSplineCurve) myCurve; ///< resulting BSpline, null if something is wrong +}; + +#endif diff --git a/src/HYDROOperations/HYDROOperations_Factory.cxx b/src/HYDROOperations/HYDROOperations_Factory.cxx new file mode 100644 index 00000000..974155b0 --- /dev/null +++ b/src/HYDROOperations/HYDROOperations_Factory.cxx @@ -0,0 +1,123 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +// global instance +HYDROOperations_Factory* FACTORY = 0; + +HYDROOperations_Factory* HYDROOperations_Factory::Factory() +{ + if (!FACTORY) { + FACTORY = new HYDROOperations_Factory; + // default operations + REGISTER_HYDRO_OPERATION(ImageComposer_ColorMaskOperator) + REGISTER_HYDRO_OPERATION(ImageComposer_CropOperator) + REGISTER_HYDRO_OPERATION(ImageComposer_CutOperator) + REGISTER_HYDRO_OPERATION(ImageComposer_FuseOperator) + } + return FACTORY; +} + +ImageComposer_Operator* HYDROOperations_Factory::Operator(const QString theName) const +{ + if (myOps.contains(theName)) { + return myOps[theName]; + } + return NULL; +} + +void HYDROOperations_Factory::Register( + ImageComposer_Operator* theOperator) +{ + FACTORY->myOps[QString(typeid(*theOperator).name())] = theOperator; +} + +HYDROOperations_Factory::HYDROOperations_Factory() +{ +} + +ImageComposer_Operator* HYDROOperations_Factory::Operator( + Handle(HYDROData_Image) theImage) const +{ + // retreive operator instance by name + ImageComposer_Operator* anOp = Operator(theImage->OperatorName()); + if (!anOp) + return anOp; + // fill arguments of the operator from theImage + theImage->Args(); + anOp->setBinArgs(theImage->Args()); + return anOp; +} + +Handle(HYDROData_Image) HYDROOperations_Factory::CreateImage( + Handle(HYDROData_Document) theDoc, const ImageComposer_Operator* theOperator) +{ + // create an object + Handle(HYDROData_Image) anImage = + Handle(HYDROData_Image)::DownCast(theDoc->CreateObject(KIND_IMAGE)); + // get data from operation + if (theOperator) { + anImage->SetOperatorName(QString(typeid(*theOperator).name())); + anImage->SetArgs(theOperator->getBinArgs()); + } + return anImage; +} + +void HYDROOperations_Factory::UpdateImage( + Handle_HYDROData_Document theDoc, Handle(HYDROData_Image) theImage) +{ + // fill by arguments and process the operation + ImageComposer_Operator* anOp = Operator(theImage); + if (anOp) { // update image only if there is an operation + ImageComposer_Image anImage1; // first referenced image + if (theImage->NbReferences()) { + Handle(HYDROData_Image) anImage = theImage->Reference(0); + anImage1 = anImage->Image(); + anImage1.setTransform(anImage->Trsf()); + } + ImageComposer_Image anImage2; // second referenced image + if (theImage->NbReferences() > 1) { + Handle(HYDROData_Image) anImage = theImage->Reference(1); + anImage2 = anImage->Image(); + anImage2.setTransform(anImage->Trsf()); + } + ImageComposer_Image aResImg = anOp->process(anImage1, anImage2); + theImage->SetImage(aResImg); + } + // change the states of this and all depended images + theImage->MustBeUpdated(true); + SetMustBeUpdatedImages(theDoc); + theImage->MustBeUpdated(false); +} + +void HYDROOperations_Factory::SetMustBeUpdatedImages( + Handle_HYDROData_Document theDoc) const +{ + bool aChanged = true; + // iterate until there is no changes because images on all level of dependency must be updated + while(aChanged) { + aChanged = false; + HYDROData_Iterator anIter(theDoc, KIND_IMAGE); + for(; anIter.More(); anIter.Next()) { + Handle(HYDROData_Image) anImage = + Handle(HYDROData_Image)::DownCast(anIter.Current()); + if (!anImage->MustBeUpdated()) { + int a, aNBRefs = anImage->NbReferences(); + for(a = 0; a < aNBRefs; a++) { + if (anImage->Reference(a)->MustBeUpdated()) { + // image references to updated => also must be updated + anImage->MustBeUpdated(true); + aChanged = true; + } + } + } + } + } +} diff --git a/src/HYDROOperations/HYDROOperations_Factory.h b/src/HYDROOperations/HYDROOperations_Factory.h new file mode 100644 index 00000000..0416e3b6 --- /dev/null +++ b/src/HYDROOperations/HYDROOperations_Factory.h @@ -0,0 +1,98 @@ +#ifndef HYDROOperations_Factory_HeaderFile +#define HYDROOperations_Factory_HeaderFile + +#include +#include +#include + +class ImageComposer_Operator; +class Handle_HYDROData_Document; + +/**\class HYDROOperations_Factory + * + * \brief This class provides the unified management of operations on images. + * + * Object is created as one global instance and allows to register and launch + * all registered operations in general way. To register a new operation just + * call REGISTER_HYDRO_OPERATION(operation_name) in some method implementation. + * This macro will call constructor of this operation (it must inherit + * ImageComposer_Operator) and factory will set up arguments and call this + * operator by demand. + */ + +class HYDROOperations_Factory +{ +public: + + //! Returns the global factory + HYDROOPERATIONS_EXPORT static HYDROOperations_Factory* Factory(); + + /** + * Registers the operator by the name, used by REGISTER_HYDRO_OPERATION macro + * \param theOperator new instance of the operator that will be used for + * processing of operation with such kind + */ + HYDROOPERATIONS_EXPORT static void Register( + ImageComposer_Operator* theOperator); + + /** + * Creates a new Image object in the data structure by the operator data. + * \param theDoc document where it must be created + * \param theOperator base operator for this Image: will be used in "Update" to recompute the image + * \returns created object related to the data structure + */ + HYDROOPERATIONS_EXPORT Handle(HYDROData_Image) CreateImage( + Handle_HYDROData_Document theDoc, const ImageComposer_Operator* theOperator); + + /** + * Updates an Image object in the data structure. If it is changed, + * sets "MustBeUpdated" flag to other depended images. + * \param theDoc document of this image (needed to update other images flags) + * \param theImage the updated image + */ + HYDROOPERATIONS_EXPORT void UpdateImage( + Handle_HYDROData_Document theDoc, Handle(HYDROData_Image) theImage); + + /** + * Returns the operator, initialized by the properties of theImage + * \param theImage data structures object, that contains all arguments + * required for creation of operation + * \returns NULL if operator type is unknown + */ + HYDROOPERATIONS_EXPORT ImageComposer_Operator* Operator( + Handle(HYDROData_Image) theImage) const; + + +protected: + + //! Not public constructor that creates only one, global instance of this factory. + HYDROOperations_Factory(); + + /** + * Returns the appropriate operator by the name + * \param theName name of the operator, equals to the operation_name constructor + * \returns NULL if operator with such name is not registered yet + */ + ImageComposer_Operator* Operator(const QString theName) const; + + /** + * Enables "MustBeUpdated" flag for Images that are depended on "MustBeUpdated" images. + * \param theDoc document where this operation is performed + */ + void SetMustBeUpdatedImages(Handle_HYDROData_Document theDoc) const; + +private: + //! Map that stores all operators, isentified by strings + typedef QMap FactoryOperators; + + FactoryOperators myOps; ///< all operators stored by a factory +}; + +/** + * Macro that is used for registered operators, see C++ of this class to see + * example of hte registration. + */ +#define REGISTER_HYDRO_OPERATION(operation_name) \ + HYDROOperations_Factory::Factory()->Register(new operation_name); + +#endif diff --git a/src/HYDROOperations/test_HYDROOperations_BSpline.cxx b/src/HYDROOperations/test_HYDROOperations_BSpline.cxx new file mode 100644 index 00000000..b17590e2 --- /dev/null +++ b/src/HYDROOperations/test_HYDROOperations_BSpline.cxx @@ -0,0 +1,64 @@ +#include + +#include +#include +#include + +void test_HYDROOperations_BSpline::testCurve() +{ + // prepare points: function of sin(x) + QList aPoints; + double x; + for(x = 0; x < 6.28; x += 0.1) + aPoints<IsClosed()); + CPPUNIT_ASSERT_EQUAL(aBS->Continuity(), GeomAbs_C2); + // check that values of BSpline are not far from original "sin" function + // in all points of the curve + for(x = 0; x < 6.29; x += 0.001) { + double aDiff = aBS->Value(x).Y() - sin(aBS->Value(x).X()); + if (aDiff < 0) aDiff = -aDiff; + CPPUNIT_ASSERT(aDiff < 3.e-6); // this number is found manually + } +} + +void test_HYDROOperations_BSpline::testPath() +{ + // prepare points: function of sin(x) + static const double aScale = 10000000.; + QList aPoints; + double x; + for(x = 0; x < 6.28; x += 0.1) + aPoints< aPolyF = aPath.toSubpathPolygons(QTransform()); + QList::iterator aFIter = aPolyF.begin(); + for(; aFIter != aPolyF.end();aFIter++) { + QPolygon aPoly = aFIter->toPolygon(); + QPolygon::iterator aPoints = aPoly.begin(); + for(; aPoints != aPoly.end(); aPoints++) { + double aDiff = aPoints->y() / aScale - sin(aPoints->x() / aScale); + if (aDiff < 0) aDiff = -aDiff; + CPPUNIT_ASSERT(aDiff < 4.e-6); // this number is found manually + } + } +} diff --git a/src/HYDROOperations/test_HYDROOperations_BSpline.h b/src/HYDROOperations/test_HYDROOperations_BSpline.h new file mode 100644 index 00000000..6eb11056 --- /dev/null +++ b/src/HYDROOperations/test_HYDROOperations_BSpline.h @@ -0,0 +1,26 @@ +#include + +class test_HYDROOperations_BSpline : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(test_HYDROOperations_BSpline); + CPPUNIT_TEST(testCurve); + CPPUNIT_TEST(testPath); + CPPUNIT_TEST_SUITE_END(); + +private: + +public: + + void setUp() {} + + void tearDown() {} + + // checks generation of BSpline curve by points + void testCurve(); + + // checks generation of QPainterPath + void testPath(); + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(test_HYDROOperations_BSpline); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_HYDROOperations_BSpline, "HYDROOperations_BSpline"); diff --git a/src/HYDROOperations/test_HYDROOperations_Factory.cxx b/src/HYDROOperations/test_HYDROOperations_Factory.cxx new file mode 100644 index 00000000..0695cc33 --- /dev/null +++ b/src/HYDROOperations/test_HYDROOperations_Factory.cxx @@ -0,0 +1,61 @@ +#include + +#include +#include +#include + +#include +#include + +void test_HYDROOperations_Factory::testCreate() +{ + Handle(HYDROData_Document) aDoc = HYDROData_Document::Document(1); + + HYDROOperations_Factory* aFactory = HYDROOperations_Factory::Factory(); + CPPUNIT_ASSERT(aFactory); + Handle(HYDROData_Image) anImage = aFactory->CreateImage(aDoc, NULL); + CPPUNIT_ASSERT(!anImage.IsNull()); + CPPUNIT_ASSERT(anImage->Image().isNull()); + + aDoc->Close(); +} + +static QImage TestImage() { + QImage aPic(50, 40, QImage::Format_RGB32); + aPic.fill(Qt::white); + QPainter aPainter(&aPic); + aPainter.drawEllipse(6, 7, 38, 30); + aPainter.drawLine(0, 40, 10, 0); + aPainter.drawLine(10, 0, 25, 35); + aPainter.drawLine(25, 35, 40, 0); + aPainter.drawLine(40, 0, 50, 40); + return aPic; +} + +void test_HYDROOperations_Factory::testCrop() +{ + Handle(HYDROData_Document) aDoc = HYDROData_Document::Document(1); + HYDROOperations_Factory* aFactory = HYDROOperations_Factory::Factory(); + Handle(HYDROData_Image) anImage = aFactory->CreateImage(aDoc, NULL); + // prepare the original image and crop-path + QImage aPic = TestImage(); + anImage->SetImage(aPic); + QPainterPath aPath(QPointF(25, 0)); + aPath.lineTo(0, 20); + aPath.lineTo(25, 40); + aPath.lineTo(50, 20); + aPath.closeSubpath(); + // prepare Composer Operation + ImageComposer_CropOperator aCropOp; + aCropOp.setArgs(Qt::red, aPath); + // create crop - image + Handle(HYDROData_Image) aCrop = aFactory->CreateImage(aDoc, &aCropOp); + CPPUNIT_ASSERT(!aCrop.IsNull()); + aCrop->AppendReference(anImage); + aFactory->UpdateImage(aDoc, aCrop); + // check crop operation was performed + CPPUNIT_ASSERT(!aCrop->Image().isNull()); + CPPUNIT_ASSERT(aCrop->Image() != aPic); + + aDoc->Close(); +} diff --git a/src/HYDROOperations/test_HYDROOperations_Factory.h b/src/HYDROOperations/test_HYDROOperations_Factory.h new file mode 100644 index 00000000..fa7e9172 --- /dev/null +++ b/src/HYDROOperations/test_HYDROOperations_Factory.h @@ -0,0 +1,25 @@ +#include + +class test_HYDROOperations_Factory : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(test_HYDROOperations_Factory); + CPPUNIT_TEST(testCreate); + CPPUNIT_TEST(testCrop); + CPPUNIT_TEST_SUITE_END(); + +private: + +public: + + void setUp() {} + + void tearDown() {} + + // checks creation of images using null operators + void testCreate(); + + // checks creation of images using crop operator + void testCrop(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(test_HYDROOperations_Factory); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_HYDROOperations_Factory, "HYDROOperations_Factory"); diff --git a/src/HYDROOperations/test_HYDROOperations_Main.cxx b/src/HYDROOperations/test_HYDROOperations_Main.cxx new file mode 100644 index 00000000..95900832 --- /dev/null +++ b/src/HYDROOperations/test_HYDROOperations_Main.cxx @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int + main( int argc, char* argv[] ) +{ + // to perform "drawing" qt tests + QApplication app(argc, argv); + + std::string testPath = (argc > 1) ? std::string(argv[1]) : ""; + + // Create the event manager and test controller + CppUnit::TestResult controller; + + // Add a listener that colllects test result + CppUnit::TestResultCollector result; + controller.addListener( &result ); + + // Add a listener that print dots as test run. + CppUnit::TextTestProgressListener progress; + controller.addListener( &progress ); + + CppUnit::TestFactoryRegistry& registry = + CppUnit::TestFactoryRegistry::getRegistry(); + // Add the top suite to the test runner + CppUnit::TestRunner runner; + runner.addTest( registry.makeTest() ); + try + { + std::cout << "Running " << testPath; + runner.run( controller, testPath ); + + std::cerr << std::endl; + + // Print test in a compiler compatible format. + CppUnit::CompilerOutputter outputter( &result, std::cerr ); + outputter.write(); + } + catch ( std::invalid_argument &e ) // Test path not resolved + { + std::cerr << std::endl + << "ERROR: " << e.what() + << std::endl; + return 0; + } + + return result.wasSuccessful() ? 0 : 1; +} -- 2.39.2