diff --git a/Doc/changelog.docu b/Doc/changelog.docu
index 5ff01c21..6908061d 100644
--- a/Doc/changelog.docu
+++ b/Doc/changelog.docu
@@ -29,6 +29,7 @@
PLY Reader: Fixed endless loop on unknown property list type
PLY Reader: Fix hang when reading directly from istream (Thanks to Paul Loré for the patch)
PLY Reader: Fix file load for ASCII PLY without a newline at the end of the file (Thanks to Mathieu Lamarre for the patch )
+PLY Reader/Writer: Support for face colors (Thanks to Steve and Barb Demlow for the patch)
OM Writer/Reader: Update file format version to 2.0. Older files can still be read, but older OpenMesh versions cannot read new format.
OM Writer/Reader: Fixed inconsistent writing/reading of edge properties
OM Writer/Reader: Add option to store status
diff --git a/src/OpenMesh/Core/IO/exporter/ExporterT.hh b/src/OpenMesh/Core/IO/exporter/ExporterT.hh
index cffe07e2..b788988c 100644
--- a/src/OpenMesh/Core/IO/exporter/ExporterT.hh
+++ b/src/OpenMesh/Core/IO/exporter/ExporterT.hh
@@ -335,14 +335,14 @@ public:
Vec3f colorf(FaceHandle _fh) const override
{
- return (mesh_.has_vertex_colors()
+ return (mesh_.has_face_colors()
? color_cast(mesh_.color(_fh))
: Vec3f(0, 0, 0));
}
Vec4f colorAf(FaceHandle _fh) const override
{
- return (mesh_.has_vertex_colors()
+ return (mesh_.has_face_colors()
? color_cast(mesh_.color(_fh))
: Vec4f(0, 0, 0, 0));
}
diff --git a/src/OpenMesh/Core/IO/reader/PLYReader.cc b/src/OpenMesh/Core/IO/reader/PLYReader.cc
index 4617afe9..2c49ff17 100644
--- a/src/OpenMesh/Core/IO/reader/PLYReader.cc
+++ b/src/OpenMesh/Core/IO/reader/PLYReader.cc
@@ -425,6 +425,12 @@ bool _PLYReader_::read_ascii(std::istream& _in, BaseImporter& _bi, const Options
// faces
for (i = 0; i < faceCount_ && !_in.eof(); ++i) {
FaceHandle fh;
+
+ c[0] = 0;
+ c[1] = 0;
+ c[2] = 0;
+ c[3] = 255;
+
for (size_t propertyIndex = 0; propertyIndex < e_it->properties_.size(); ++propertyIndex) {
PropertyInfo prop = e_it->properties_[propertyIndex];
switch (prop.property) {
@@ -456,6 +462,38 @@ bool _PLYReader_::read_ascii(std::istream& _in, BaseImporter& _bi, const Options
++complex_faces;
break;
+ case COLORRED:
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ _in >> tmp;
+ c[0] = static_cast (tmp * 255.0f);
+ } else
+ _in >> c[0];
+ break;
+
+ case COLORGREEN:
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ _in >> tmp;
+ c[1] = static_cast (tmp * 255.0f);
+ } else
+ _in >> c[1];
+ break;
+
+ case COLORBLUE:
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ _in >> tmp;
+ c[2] = static_cast (tmp * 255.0f);
+ } else
+ _in >> c[2];
+ break;
+
+ case COLORALPHA:
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ _in >> tmp;
+ c[3] = static_cast (tmp * 255.0f);
+ } else
+ _in >> c[3];
+ break;
+
case CUSTOM_PROP:
if (_opt.check(Options::Custom) && fh.is_valid())
readCustomProperty(_in, _bi, fh, prop.name, prop.value, prop.listIndexType);
@@ -468,7 +506,8 @@ bool _PLYReader_::read_ascii(std::istream& _in, BaseImporter& _bi, const Options
break;
}
}
-
+ if (_opt.face_has_color())
+ _bi.set_color(fh, Vec4uc(c));
}
}
else
@@ -568,29 +607,26 @@ bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap
readValue(prop.value, _in, t[1]);
break;
case COLORRED:
- if (prop.value == ValueTypeFLOAT32 ||
- prop.value == ValueTypeFLOAT) {
- readValue(prop.value, _in, tmp);
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
- c[0] = static_cast (tmp * 255.0f);
+ c[0] = static_cast (tmp * 255.0f);
}
else
- readInteger(prop.value, _in, c[0]);
+ readInteger(prop.value, _in, c[0]);
break;
case COLORGREEN:
- if (prop.value == ValueTypeFLOAT32 ||
- prop.value == ValueTypeFLOAT) {
- readValue(prop.value, _in, tmp);
- c[1] = static_cast (tmp * 255.0f);
- }
- else
- readInteger(prop.value, _in, c[1]);
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
+ c[1] = static_cast (tmp * 255.0f);
+ }
+ else
+ readInteger(prop.value, _in, c[1]);
break;
case COLORBLUE:
- if (prop.value == ValueTypeFLOAT32 ||
- prop.value == ValueTypeFLOAT) {
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
readValue(prop.value, _in, tmp);
c[2] = static_cast (tmp * 255.0f);
}
@@ -599,8 +635,7 @@ bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap
break;
case COLORALPHA:
- if (prop.value == ValueTypeFLOAT32 ||
- prop.value == ValueTypeFLOAT) {
+ if (prop.value == ValueTypeFLOAT32 || prop.value == ValueTypeFLOAT) {
readValue(prop.value, _in, tmp);
c[3] = static_cast (tmp * 255.0f);
}
@@ -634,6 +669,12 @@ bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap
else if (e_it->element_ == FACE) {
for (unsigned i = 0; i < e_it->count_ && !_in.eof(); ++i) {
FaceHandle fh;
+
+ c[0] = 0;
+ c[1] = 0;
+ c[2] = 0;
+ c[3] = 255;
+
for (size_t propertyIndex = 0; propertyIndex < e_it->properties_.size(); ++propertyIndex)
{
PropertyInfo prop = e_it->properties_[propertyIndex];
@@ -668,7 +709,38 @@ bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap
if (!fh.is_valid())
++complex_faces;
break;
-
+ case COLORRED:
+ if (prop.value == ValueTypeFLOAT32 ||
+ prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
+ c[0] = static_cast (tmp * 255.0f);
+ } else
+ readInteger(prop.value, _in, c[0]);
+ break;
+ case COLORGREEN:
+ if (prop.value == ValueTypeFLOAT32 ||
+ prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
+ c[1] = static_cast (tmp * 255.0f);
+ } else
+ readInteger(prop.value, _in, c[1]);
+ break;
+ case COLORBLUE:
+ if (prop.value == ValueTypeFLOAT32 ||
+ prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
+ c[2] = static_cast (tmp * 255.0f);
+ } else
+ readInteger(prop.value, _in, c[2]);
+ break;
+ case COLORALPHA:
+ if (prop.value == ValueTypeFLOAT32 ||
+ prop.value == ValueTypeFLOAT) {
+ readValue(prop.value, _in, tmp);
+ c[3] = static_cast (tmp * 255.0f);
+ } else
+ readInteger(prop.value, _in, c[3]);
+ break;
case CUSTOM_PROP:
if (_opt.check(Options::Custom) && fh.is_valid())
readCustomProperty(_in, _bi, fh, prop.name, prop.value, prop.listIndexType);
@@ -681,6 +753,8 @@ bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap
break;
}
}
+ if (_opt.face_has_color())
+ _bi.set_color(fh, Vec4uc(c));
}
}
else {
@@ -1347,6 +1421,30 @@ bool _PLYReader_::can_u_read(std::istream& _is) const {
options_ += Options::ColorFloat;
}
}
+ else if (elementName == "face") {
+ if (propertyName == "red") {
+ entry = PropertyInfo(COLORRED, valueType);
+ options_ += Options::FaceColor;
+ if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
+ options_ += Options::ColorFloat;
+ } else if (propertyName == "green") {
+ entry = PropertyInfo(COLORGREEN, valueType);
+ options_ += Options::FaceColor;
+ if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
+ options_ += Options::ColorFloat;
+ } else if (propertyName == "blue") {
+ entry = PropertyInfo(COLORBLUE, valueType);
+ options_ += Options::FaceColor;
+ if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
+ options_ += Options::ColorFloat;
+ } else if (propertyName == "alpha") {
+ entry = PropertyInfo(COLORALPHA, valueType);
+ options_ += Options::FaceColor;
+ options_ += Options::ColorAlpha;
+ if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
+ options_ += Options::ColorFloat;
+ }
+ }
//not a special property, load as custom
if (entry.value == Unsupported){
diff --git a/src/OpenMesh/Core/IO/writer/PLYWriter.cc b/src/OpenMesh/Core/IO/writer/PLYWriter.cc
index 0b2ab876..49db3ec6 100644
--- a/src/OpenMesh/Core/IO/writer/PLYWriter.cc
+++ b/src/OpenMesh/Core/IO/writer/PLYWriter.cc
@@ -122,14 +122,6 @@ write(std::ostream& _os, BaseExporter& _be, Options _opt, std::streamsize _preci
omerr() << "[PLYWriter] : Warning: Face normals are not supported and thus not exported! " << std::endl;
}
- if ( _opt.check(Options::FaceColor) ) {
- // Face normals are not supported
- // Uncheck these options and output message that
- // they are not written out even though they were requested
- _opt.unset(Options::FaceColor);
- omerr() << "[PLYWriter] : Warning: Face colors are not supported and thus not exported! " << std::endl;
- }
-
options_ = _opt;
@@ -313,6 +305,24 @@ void _PLYWriter_::write_header(std::ostream& _out, BaseExporter& _be, Options& _
_out << "element face " << _be.n_faces() << '\n';
_out << "property list uchar int vertex_indices" << '\n';
+ if ( _opt.face_has_color() ){
+ if ( _opt.color_is_float() ) {
+ _out << "property float red" << '\n';
+ _out << "property float green" << '\n';
+ _out << "property float blue" << '\n';
+
+ if ( _opt.color_has_alpha() )
+ _out << "property float alpha" << '\n';
+ } else {
+ _out << "property uchar red" << '\n';
+ _out << "property uchar green" << '\n';
+ _out << "property uchar blue" << '\n';
+
+ if ( _opt.color_has_alpha() )
+ _out << "property uchar alpha" << '\n';
+ }
+ }
+
_ofProps = writeCustomTypeHeader(_out, _be.kernel()->fprops_begin(), _be.kernel()->fprops_end());
_out << "end_header" << '\n';
@@ -335,6 +345,7 @@ write_ascii(std::ostream& _out, BaseExporter& _be, Options _opt) const
OpenMesh::Vec4f cAf;
OpenMesh::Vec2f t;
VertexHandle vh;
+ FaceHandle fh;
std::vector vhandles;
std::vector vProps;
@@ -400,12 +411,37 @@ write_ascii(std::ostream& _out, BaseExporter& _be, Options _opt) const
// faces (indices starting at 0)
for (i=0, nF=int(_be.n_faces()); i::iterator iter = fProps.begin(); iter < fProps.end(); ++iter)
write_customProp(_out,*iter,i);
@@ -562,6 +598,7 @@ write_binary(std::ostream& _out, BaseExporter& _be, Options _opt) const
OpenMesh::Vec4uc c;
OpenMesh::Vec4f cf;
VertexHandle vh;
+ FaceHandle fh;
std::vector vhandles;
// vProps and fProps will be empty, until custom properties are supported by the binary writer
@@ -624,12 +661,35 @@ write_binary(std::ostream& _out, BaseExporter& _be, Options _opt) const
for (i=0, nF=int(_be.n_faces()); i::iterator iter = fProps.begin(); iter < fProps.end(); ++iter)
write_customProp(_out,*iter,i);
}
diff --git a/src/OpenMesh/Core/IO/writer/PLYWriter.hh b/src/OpenMesh/Core/IO/writer/PLYWriter.hh
index fe2b8212..d11571ca 100644
--- a/src/OpenMesh/Core/IO/writer/PLYWriter.hh
+++ b/src/OpenMesh/Core/IO/writer/PLYWriter.hh
@@ -82,6 +82,7 @@ namespace IO {
currently supported options:
- VertexColors
+ - FaceColors
- Binary
- Binary -> MSB
*/
diff --git a/src/Unittests/TestFiles/cube-minimal-faceColors.ply b/src/Unittests/TestFiles/cube-minimal-faceColors.ply
new file mode 100644
index 00000000..4b97743e
--- /dev/null
+++ b/src/Unittests/TestFiles/cube-minimal-faceColors.ply
@@ -0,0 +1,33 @@
+ply
+format ascii 1.0
+comment VCGLIB generated
+element vertex 8
+property float x
+property float y
+property float z
+element face 12
+property list uchar int vertex_indices
+property float red
+property float green
+property float blue
+end_header
+-1 -1 -1
+1 -1 -1
+1 1 -1
+-1 1 -1
+-1 -1 1
+1 -1 1
+1 1 1
+-1 1 1
+3 0 1 2 0.419608 0.462745 0.698039
+3 0 2 3 1.0 0.552941 0.419608
+3 5 4 7 0.698039 1.0 0.619608
+3 5 7 6 0.419608 1.0 0.529412
+3 6 2 1 0.643137 0.419608 0.698039
+3 6 1 5 0.643137 0.419608 1.0
+3 3 7 4 0.698039 0.552941 1.0
+3 3 4 0 1.0 0.552941 0.419608
+3 7 3 2 0.419608 0.698039 1.0
+3 7 2 6 0.419608 0.698039 0.529412
+3 5 1 0 1.0 0.419608 1.0
+3 5 0 4 0.698039 1.0 1.0
diff --git a/src/Unittests/unittests_read_write_PLY.cc b/src/Unittests/unittests_read_write_PLY.cc
index dd83ff81..bd1a4533 100644
--- a/src/Unittests/unittests_read_write_PLY.cc
+++ b/src/Unittests/unittests_read_write_PLY.cc
@@ -800,4 +800,143 @@ TEST_F(OpenMeshReadWritePLY, FailOnUnknownPropertyTypeForLists) {
EXPECT_EQ(0u, mesh_.n_faces()) << "The number of loaded faces is not correct!";
}
+/*
+ * Load an ASCII PLY file of a cube with float RGB face colors
+ */
+TEST_F(OpenMeshReadWritePLY, LoadSimplePLYWithFaceColors) {
+
+ mesh_.clear();
+
+ mesh_.request_face_colors();
+
+ OpenMesh::IO::Options options;
+ options += OpenMesh::IO::Options::FaceColor;
+
+ bool ok = OpenMesh::IO::read_mesh(mesh_, "cube-minimal-faceColors.ply",options);
+
+ EXPECT_TRUE(ok) << "Unable to load cube-minimal-faceColors.ply";
+
+ EXPECT_EQ(8u , mesh_.n_vertices()) << "The number of loaded vertices is not correct!";
+ EXPECT_EQ(18u, mesh_.n_edges()) << "The number of loaded edges is not correct!";
+ EXPECT_EQ(12u, mesh_.n_faces()) << "The number of loaded faces is not correct!";
+
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(0))[0] ) << "Wrong face color at face 0";
+ EXPECT_EQ(117, mesh_.color(mesh_.face_handle(0))[1] ) << "Wrong face color at face 0";
+ EXPECT_EQ(177, mesh_.color(mesh_.face_handle(0))[2] ) << "Wrong face color at face 0";
+
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(3))[0] ) << "Wrong face color at face 3";
+ EXPECT_EQ(255, mesh_.color(mesh_.face_handle(3))[1] ) << "Wrong face color at face 3";
+ EXPECT_EQ(135, mesh_.color(mesh_.face_handle(3))[2] ) << "Wrong face color at face 3";
+
+ EXPECT_EQ(163, mesh_.color(mesh_.face_handle(4))[0] ) << "Wrong face color at face 4";
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(4))[1] ) << "Wrong face color at face 4";
+ EXPECT_EQ(177, mesh_.color(mesh_.face_handle(4))[2] ) << "Wrong face color at face 4";
+
+ EXPECT_EQ(255, mesh_.color(mesh_.face_handle(7))[0] ) << "Wrong face color at face 7";
+ EXPECT_EQ(140, mesh_.color(mesh_.face_handle(7))[1] ) << "Wrong face color at face 7";
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(7))[2] ) << "Wrong face color at face 7";
+
+ EXPECT_FALSE(options.vertex_has_normal()) << "Wrong user options are returned!";
+ EXPECT_FALSE(options.vertex_has_texcoord()) << "Wrong user options are returned!";
+ EXPECT_FALSE(options.vertex_has_color()) << "Wrong user options are returned!";
+ EXPECT_TRUE(options.face_has_color()) << "Wrong user options are returned!";
+ EXPECT_FALSE(options.color_has_alpha()) << "Wrong user options are returned!";
+ EXPECT_FALSE(options.is_binary()) << "Wrong user options are returned!";
+
+ mesh_.release_face_colors();
+}
+
+/*
+ * Write and read PLY files with face colors in various formats
+ */
+TEST_F(OpenMeshReadWritePLY, WriteAndReadPLYWithFaceColors) {
+ struct Format {
+ Format(const char* outFileName, OpenMesh::IO::Options options) :
+ _outFileName(outFileName),
+ _options(options)
+ {}
+ const char* _outFileName;
+ const OpenMesh::IO::Options _options;
+ }
+ formats[] =
+ {
+ Format("cube-minimal-faceColors_ascii_uchar.ply",
+ OpenMesh::IO::Options::Default),
+ Format("cube-minimal-faceColors_ascii_float.ply",
+ OpenMesh::IO::Options::ColorFloat),
+ Format("cube-minimal-faceColors_binary_uchar.ply",
+ OpenMesh::IO::Options::Binary),
+ Format("cube-minimal-faceColors_binary_float.ply",
+ OpenMesh::IO::Options::Binary | OpenMesh::IO::Options::ColorFloat),
+ // Test writing/reading alpha values (all default 1.0/255), but the test mesh
+ // Color type has no alpha channel so there's nothing to test below
+ Format("cube-minimal-faceColors_alpha_ascii_uchar.ply",
+ OpenMesh::IO::Options::ColorAlpha),
+ Format("cube-minimal-faceColors_alpha_ascii_float.ply",
+ OpenMesh::IO::Options::ColorFloat | OpenMesh::IO::Options::ColorAlpha),
+ Format("cube-minimal-faceColors_alpha_binary_uchar.ply",
+ OpenMesh::IO::Options::Binary | OpenMesh::IO::Options::ColorAlpha),
+ Format("cube-minimal-faceColors_alpha_binary_float.ply",
+ OpenMesh::IO::Options::Binary | OpenMesh::IO::Options::ColorFloat | OpenMesh::IO::Options::ColorAlpha),
+ };
+
+ for (size_t i = 0; i < sizeof(formats) / sizeof(Format); ++i)
+ {
+ const char* outFileName = formats[i]._outFileName;
+
+ mesh_.clear();
+
+ mesh_.request_face_colors();
+
+ OpenMesh::IO::Options options;
+ options += OpenMesh::IO::Options::FaceColor;
+
+ bool ok = OpenMesh::IO::read_mesh(mesh_, "cube-minimal-faceColors.ply", options);
+
+ EXPECT_TRUE(ok) << "Unable to load cube-minimal-faceColors.ply";
+
+ options = formats[i]._options;
+ options += OpenMesh::IO::Options::FaceColor;
+ ok = OpenMesh::IO::write_mesh(mesh_, outFileName, options);
+ EXPECT_TRUE(ok) << "Unable to write " << outFileName;
+
+ // Reset for reading: let the reader determine binary/float/etc.
+ options = OpenMesh::IO::Options::FaceColor;
+ mesh_.clear();
+ ok = OpenMesh::IO::read_mesh(mesh_, outFileName, options);
+ EXPECT_TRUE(ok) << "Unable to load " << outFileName;
+
+ EXPECT_EQ(8u , mesh_.n_vertices()) << "The number of loaded vertices is not correct: " << outFileName;
+ EXPECT_EQ(18u, mesh_.n_edges()) << "The number of loaded edges is not correct: " << outFileName;
+ EXPECT_EQ(12u, mesh_.n_faces()) << "The number of loaded faces is not correct: " << outFileName;
+
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(0))[0] ) << "Wrong face color at face 0: " << outFileName;
+ EXPECT_EQ(117, mesh_.color(mesh_.face_handle(0))[1] ) << "Wrong face color at face 0: " << outFileName;
+ EXPECT_EQ(177, mesh_.color(mesh_.face_handle(0))[2] ) << "Wrong face color at face 0: " << outFileName;
+
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(3))[0] ) << "Wrong face color at face 3: " << outFileName;
+ EXPECT_EQ(255, mesh_.color(mesh_.face_handle(3))[1] ) << "Wrong face color at face 3: " << outFileName;
+ EXPECT_EQ(135, mesh_.color(mesh_.face_handle(3))[2] ) << "Wrong face color at face 3: " << outFileName;
+
+ EXPECT_EQ(163, mesh_.color(mesh_.face_handle(4))[0] ) << "Wrong face color at face 4: " << outFileName;
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(4))[1] ) << "Wrong face color at face 4: " << outFileName;
+ EXPECT_EQ(177, mesh_.color(mesh_.face_handle(4))[2] ) << "Wrong face color at face 4: " << outFileName;
+
+ EXPECT_EQ(255, mesh_.color(mesh_.face_handle(7))[0] ) << "Wrong face color at face 7: " << outFileName;
+ EXPECT_EQ(140, mesh_.color(mesh_.face_handle(7))[1] ) << "Wrong face color at face 7: " << outFileName;
+ EXPECT_EQ(107, mesh_.color(mesh_.face_handle(7))[2] ) << "Wrong face color at face 7: " << outFileName;
+
+ EXPECT_FALSE(options.vertex_has_normal()) << "Wrong user options are returned: " << outFileName;
+ EXPECT_FALSE(options.vertex_has_texcoord()) << "Wrong user options are returned: " << outFileName;
+ EXPECT_FALSE(options.vertex_has_color()) << "Wrong user options are returned: " << outFileName;
+ EXPECT_TRUE(options.face_has_color()) << "Wrong user options are returned: " << outFileName;
+ EXPECT_EQ(formats[i]._options.color_is_float(), options.color_is_float()) <<
+ "Wrong user options are returned: " << outFileName;
+ EXPECT_EQ(formats[i]._options.is_binary(), options.is_binary()) <<
+ "Wrong user options are returned: " << outFileName;
+
+ mesh_.release_face_colors();
+ }
+}
+
}