Merge topic 'image-io'

24d022b02 Implement and test ImageReader and ImageWriter capabilities in the io library

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Kenneth Moreland <kmorel@sandia.gov>
Merge-request: !1967
This commit is contained in:
Nickolas Davis 2020-05-13 23:20:10 +00:00 committed by Kitware Robot
commit 3a42ebf7bd
15 changed files with 1338 additions and 11 deletions

@ -0,0 +1,32 @@
# Implemented PNG/PPM image Readers/Writers
The original implementation of writing image data was only performed as a
proxy through the Canvas rendering class. In order to implement true support
for image-based regression testing, this interface needed to be expanded upon
to support reading/writing arbitrary image data and storing it in a `vtkm::DataSet`.
Using the new `vtkm::io::PNGReader` and `vtkm::io::PPMReader` it is possible
to read data from files and Cavases directly and store them as a point field
in a 2D uniform `vtkm::DataSet`
```cpp
auto reader = vtkm::io::PNGReader();
auto imageDataSet = reader.ReadFromFile("read_image.png");
```
Similarly, the new `vtkm::io::PNGWriter` and `vtkm::io::PPMWriter` make it possible
to write out a 2D uniform `vtkm::DataSet` directly to a file.
```cpp
auto writer = vtkm::io::PNGWriter();
writer.WriteToFile("write_image.png", imageDataSet);
```
If canvas data is to be written out, the reader provides a method for converting
a canvas's data to a `vtkm::DataSet`.
```cpp
auto reader = vtkm::io::PNGReader();
auto dataSet = reader.CreateImageDataSet(canvas);
auto writer = vtkm::io::PNGWriter();
writer.WriteToFile("output.png", dataSet);
```

@ -97,6 +97,7 @@ void RunTests()
int UnitTestLogging(int, char* [])
{
// Test that parameterless init works:
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Log before intialize");
vtkm::cont::InitLogging();
RunTests();

@ -13,6 +13,9 @@ set(headers
ErrorIO.h
DecodePNG.h
EncodePNG.h
ImageReader.h
ImageWriter.h
PixelTypes.h
VTKDataSetReader.h
VTKDataSetReaderBase.h
VTKPolyDataReader.h
@ -23,17 +26,28 @@ set(headers
VTKDataSetWriter.h
)
set(template_sources
ImageReader.hxx
ImageWriter.hxx
PixelTypes.hxx
)
set(sources
DecodePNG.cxx
EncodePNG.cxx
)
vtkm_declare_headers(${headers})
vtkm_declare_headers(
${headers}
${template_sources}
)
vtkm_library( NAME vtkm_io
SOURCES ${sources}
HEADERS ${headers}
)
vtkm_library(
NAME vtkm_io
SOURCES ${sources}
HEADERS ${headers}
TEMPLATE_SOURCES ${template_sources}
)
target_link_libraries(vtkm_io PUBLIC vtkm_cont PRIVATE vtkm_lodepng)

@ -13,7 +13,6 @@
#include <vtkm/cont/Logging.h>
#include <vtkm/internal/Configure.h>
VTKM_THIRDPARTY_PRE_INCLUDE
#include <vtkm/thirdparty/lodepng/vtkmlodepng/lodepng.h>
VTKM_THIRDPARTY_POST_INCLUDE

155
vtkm/io/ImageReader.h Normal file

@ -0,0 +1,155 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageReader_h
#define vtk_m_io_ImageReader_h
#include <vtkm/Types.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/DataSet.h>
namespace vtkm
{
// Forward Declare
namespace rendering
{
class Canvas;
} // namespace rendering
namespace io
{
/// \brief Manages reading, and loading data from images
///
/// \c BaseImageReader implements methods for loading imaging data from a canvas or
/// ArrayHandle and storing that data in a vtkm::cont::DataSet. Image rgb values
/// are represented as a point field in a 2D uniform dataset.
///
/// \c BaseImageReader can be constructed from a file, canvas, or ArrayHandle. It can
/// also be empy constructed and filled in with a dataset later.
///
/// \c BaseImageReader implements virtual methods for reading files. Ideally,
/// these methods will be overriden in various subclasses to implement specific
/// functionality for reading data to specific image file-types.
///
/// \c The templated type is used when Filling the ImageDataSet.
///
class BaseImageReader
{
public:
/// Constructs an emtpy BaseImageReader.
///
BaseImageReader() = default;
explicit BaseImageReader(const vtkm::Id& maxColorValue)
: MaxColorValue(maxColorValue)
{
}
~BaseImageReader() noexcept = default;
/// Reads image data from a file. Meant to be implemented in overriden
/// image-specific classes
///
virtual vtkm::cont::DataSet ReadFromFile(const std::string& fileName) = 0;
/// Creates an ImageDataSet from a Canvas object
///
vtkm::cont::DataSet CreateImageDataSet(const vtkm::rendering::Canvas& canvas);
/// Creates an ImageDataSet from a RGBA 32bit float color buffer
/// Assumes the color buffer is stored in row-major ordering
///
vtkm::cont::DataSet CreateImageDataSet(const vtkm::cont::ArrayHandle<vtkm::Vec4f_32>& colorBuffer,
const vtkm::Id& width,
const vtkm::Id& height);
const std::string& GetPointFieldName() const { return this->PointFieldName; }
protected:
vtkm::cont::DataSet InitializeImageDataSet(const vtkm::Id& width, const vtkm::Id& height);
std::string PointFieldName = "pixel-data";
vtkm::Id MaxColorValue{ 0 };
};
/// \brief Manages reading images using the PNG format via lodepng
///
/// \c PNGReader extends BaseImageReader and implements reading images in a valid
/// PNG format. It utilizes lodepng's decode file functions to read
/// PNG images that are automatically compressed to optimal sizes relative to
/// the actual bit complexity of the image.
///
/// \c PNGReader will automatically upsample/downsample read image data
/// to a 16 bit RGB no matter how the image is compressed. It is up to the user to
/// decide the pixel format for input PNGs
class PNGReader : public BaseImageReader
{
using Superclass = BaseImageReader;
public:
using Superclass::Superclass;
PNGReader() = default;
~PNGReader() noexcept = default;
/// Reads PNG data from the provided file and stores it
/// as a 16bit RGB value
///
vtkm::cont::DataSet ReadFromFile(const std::string& fileName) override;
/// Reads PNG data from the provided file and stores it
/// according to the method's templated PixelType
///
template <typename PixelType>
vtkm::cont::DataSet ReadFromFile(const std::string& fileName);
};
/// \brief Manages reading images using the PNM format
///
/// \c PNMImage extends BaseImage, and implements reading images from a
/// valid PNM format (for magic number P6). More details on the PNM
/// format can be found here: http://netpbm.sourceforge.net/doc/ppm.html
///
/// When a file is read the parsed MagicNumber and MaxColorSize provided
/// are utilized to correctly parse the bits from the file
class PNMReader : public BaseImageReader
{
using Superclass = BaseImageReader;
public:
using Superclass::Superclass;
PNMReader() = default;
~PNMReader() noexcept = default;
/// Attempts to read the provided file into a DataSet object.
/// Will pull the image's MaxColorValue from the file and then Decode
/// with the appropriate RGB PixelType bit depth.
///
vtkm::cont::DataSet ReadFromFile(const std::string& fileName) override;
protected:
/// Reads image data from the provided inStream with the supplied width/height
/// Stores the data in a vector of PixelType which is converted to an DataSet
///
template <typename PixelType>
vtkm::cont::DataSet DecodeFile(std::ifstream& inStream,
const vtkm::Id& width,
const vtkm::Id& height);
// This is set to only work for P6 pnm image types for now (ie ppm)
std::string MagicNumber{ "P6" };
};
} // namespace io
} // namespace vtkm
#include <vtkm/io/ImageReader.hxx>
#endif

176
vtkm/io/ImageReader.hxx Normal file

@ -0,0 +1,176 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageReader_hxx
#define vtk_m_io_ImageReader_hxx
#include <vtkm/cont/CellSetStructured.h>
#include <vtkm/cont/DataSetBuilderUniform.h>
#include <vtkm/cont/DataSetFieldAdd.h>
#include <vtkm/io/ImageReader.h>
#include <vtkm/io/PixelTypes.h>
#include <vtkm/rendering/Canvas.h>
VTKM_THIRDPARTY_PRE_INCLUDE
#include <vtkm/thirdparty/lodepng/vtkmlodepng/lodepng.h>
VTKM_THIRDPARTY_POST_INCLUDE
namespace vtkm
{
namespace io
{
// Start BaseReaderImage Class Template Implementations
VTKM_CONT
vtkm::cont::DataSet BaseImageReader::CreateImageDataSet(const vtkm::rendering::Canvas& canvas)
{
return this->CreateImageDataSet(canvas.GetColorBuffer(), canvas.GetWidth(), canvas.GetHeight());
}
VTKM_CONT
vtkm::cont::DataSet BaseImageReader::CreateImageDataSet(
const vtkm::cont::ArrayHandle<vtkm::Vec4f_32>& colorBuffer,
const vtkm::Id& width,
const vtkm::Id& height)
{
vtkm::cont::ArrayHandle<vtkm::Vec4f_32>::ReadPortalType colorPortal = colorBuffer.ReadPortal();
std::vector<vtkm::Vec4f_32> fieldData;
for (vtkm::Id yIndex = 0; yIndex < height; yIndex++)
{
for (vtkm::Id xIndex = 0; xIndex < width; xIndex++)
{
vtkm::Vec4f_32 tuple = colorPortal.Get(yIndex * width + xIndex);
fieldData.push_back(tuple);
}
}
auto dataSet = this->InitializeImageDataSet(width, height);
vtkm::cont::DataSetFieldAdd dsf;
dsf.AddPointField(dataSet, this->PointFieldName, fieldData);
return dataSet;
}
VTKM_CONT
vtkm::cont::DataSet BaseImageReader::InitializeImageDataSet(const vtkm::Id& width,
const vtkm::Id& height)
{
vtkm::cont::DataSetBuilderUniform dsb;
vtkm::Id2 dimensions(width, height);
return dsb.Create(dimensions);
}
// End BaseReaderImage Class Template Implementations
// Start PNGReader Class Template Implementations
VTKM_CONT
vtkm::cont::DataSet PNGReader::ReadFromFile(const std::string& fileName)
{
return this->ReadFromFile<io::RGBPixel_16>(fileName);
}
VTKM_CONT
template <typename PixelType>
vtkm::cont::DataSet PNGReader::ReadFromFile(const std::string& fileName)
{
unsigned char* imageData;
unsigned uwidth, uheight;
vtkm::Id width, height;
vtkm::png::lodepng_decode_file(&imageData,
&uwidth,
&uheight,
fileName.c_str(),
PixelType::PNG_COLOR_TYPE,
PixelType::BIT_DEPTH);
width = static_cast<vtkm::Id>(uwidth);
height = static_cast<vtkm::Id>(uheight);
// Fill in the data starting from the end (Images are read Top-Left to Bottom-Right,
// but are stored from Bottom-Left to Top-Right)
std::vector<vtkm::Vec4f_32> fieldData;
for (vtkm::Id yIndex = static_cast<vtkm::Id>(height - 1); yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < static_cast<vtkm::Id>(width); xIndex++)
{
vtkm::Id index = static_cast<vtkm::Id>(yIndex * width + xIndex);
fieldData.push_back(PixelType(imageData, index).ToVec4f());
}
}
auto dataSet = this->InitializeImageDataSet(width, height);
vtkm::cont::DataSetFieldAdd dsf;
dsf.AddPointField(dataSet, this->PointFieldName, fieldData);
free(imageData);
return dataSet;
}
// End PNGReader Class Template Implementations
// Start PNMReader Class Template Implementations
VTKM_CONT
vtkm::cont::DataSet PNMReader::ReadFromFile(const std::string& fileName)
{
std::ifstream inStream(fileName.c_str(), std::ios_base::binary | std::ios_base::in);
vtkm::Id width;
vtkm::Id height;
std::string val;
inStream >> val;
if (this->MagicNumber != val)
{
throw vtkm::cont::ErrorBadValue("MagicNumber: " + this->MagicNumber + " in file: " + fileName +
" did not match: " + val);
}
inStream >> width >> height >> this->MaxColorValue;
inStream.get();
switch (this->MaxColorValue)
{
case 255:
return this->DecodeFile<io::RGBPixel_8>(inStream, width, height);
case 65535:
return this->DecodeFile<io::RGBPixel_16>(inStream, width, height);
default:
throw vtkm::cont::ErrorBadValue("MaxColorValue: " + std::to_string(this->MaxColorValue) +
" from file: " + fileName + " was not one of: {8, 16}");
}
}
VTKM_CONT
template <typename PixelType>
vtkm::cont::DataSet PNMReader::DecodeFile(std::ifstream& inStream,
const vtkm::Id& width,
const vtkm::Id& height)
{
vtkm::UInt32 imageSize = static_cast<vtkm::UInt32>(width * height * PixelType::BYTES_PER_PIXEL);
std::vector<unsigned char> imageData(imageSize);
inStream.read((char*)imageData.data(), imageSize);
// Fill in the data starting from the end (Images are read Top-Left to Bottom-Right,
// but are stored from Bottom-Left to Top-Right)
std::vector<vtkm::Vec4f_32> fieldData;
for (vtkm::Id yIndex = height - 1; yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < width; xIndex++)
{
vtkm::Id index = yIndex * width + xIndex;
fieldData.push_back(PixelType(imageData.data(), index).ToVec4f());
}
}
auto dataSet = this->InitializeImageDataSet(width, height);
vtkm::cont::DataSetFieldAdd dsf;
dsf.AddPointField(dataSet, this->PointFieldName, fieldData);
return dataSet;
}
// End PNMReader Class Template Implementations
} // namespace io
} // namespace vtkm
#endif

140
vtkm/io/ImageWriter.h Normal file

@ -0,0 +1,140 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageWriter_h
#define vtk_m_io_ImageWriter_h
#include <vtkm/Types.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/DataSet.h>
namespace vtkm
{
namespace io
{
/// \brief Manages writing, and loading data from images
///
/// \c BaseImageWriter implements methods for loading imaging data from a canvas or
/// ArrayHandle and storing that data in a vtkm::cont::DataSet. Image rgb values
/// are represented as a point field in a 2D uniform dataset.
///
/// \c BaseImageWriter can be constructed from a file, canvas, or ArrayHandle. It can
/// also be empy constructed and filled in with a dataset later.
///
/// \c BaseImageWriter implements virtual methods for writing files. Ideally,
/// these methods will be overriden in various subclasses to implement specific
/// functionality for writing data to specific image file-types.
///
class BaseImageWriter
{
public:
/// Constructs an emtpy BaseImageWriter.
///
BaseImageWriter() = default;
explicit BaseImageWriter(const vtkm::Id& maxColorValue)
: MaxColorValue(maxColorValue)
{
}
~BaseImageWriter() noexcept = default;
/// Write and store ImageDataSet to a file. Meant to be implemented in
/// overriden image-specific classes
///
virtual void WriteToFile(const std::string& fileName,
const vtkm::cont::DataSet& dataSet) const = 0;
vtkm::Id GetImageWidth(vtkm::cont::DataSet dataSet) const;
vtkm::Id GetImageHeight(vtkm::cont::DataSet dataSet) const;
const std::string& GetPointFieldName() const { return this->PointFieldName; }
void SetMaxColorValue(const vtkm::Id& maxColorValue) { this->MaxColorValue = maxColorValue; }
protected:
std::string PointFieldName = "pixel-data";
vtkm::Id MaxColorValue{ 0 };
};
/// \brief Manages writing images using the PNM format
///
/// \c PNMWriter extends BaseImageWriter, and implements writing images in a
/// valid PNM format (for magic number P6). More details on the PNM
/// format can be found here: http://netpbm.sourceforge.net/doc/ppm.html
///
/// When a file is writen the MaxColorValue found in the file is used to
/// determine the PixelType required to stored PixelType is instead dependent
/// upon the read MaxColorValue obtained from the file
class PNMWriter : public BaseImageWriter
{
using Superclass = BaseImageWriter;
public:
using Superclass::Superclass;
PNMWriter() = default;
~PNMWriter() noexcept = default;
/// Attempts to write the ImageDataSet to a PNM file. The MaxColorValue
/// set in the file with either be selected from the stored MaxColorValue
/// member variable, or from the templated type if MaxColorValue hasn't been
/// set from a read file.
///
void WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const override;
protected:
/// Writes image data stored in ImageDataSet to the provided outStream
/// Casts the data to the provided PixelType
///
template <typename PixelType>
void EncodeFile(std::ofstream& outStream, const vtkm::cont::DataSet& dataSet) const;
// Currently only works with P6 PNM files (PPM)
std::string MagicNumber{ "P6" };
};
/// \brief Manages writing images using the PNG format via lodepng
///
/// \c PNGWriter extends BaseImageWriter and implements writing images in a valid
/// PNG format. It utilizes lodepng's encode file functions to write
/// PNG images that are automatically compressed to optimal sizes relative to
/// the actual bit complexity of the image.
///
/// \c PNGImage will automatically upsample/downsample written image data
/// to the supplied templated PixelType. For example, it is possible to write
/// a 1-bit greyscale image into a 16bit RGB PNG object. It is up to the user to
/// decide the pixel format for output PNGs
class PNGWriter : public BaseImageWriter
{
using Superclass = BaseImageWriter;
public:
using Superclass::Superclass;
PNGWriter() = default;
~PNGWriter() noexcept = default;
/// Writes stored data matched to the class's templated type
/// to a file in PNG format. Relies upon the lodepng encoding
/// method to optimize compression and choose the best storage format.
///
void WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const override;
/// Writes stored data matched to the method's templated type
/// to a file in PNG format. Relies upon the lodepng encoding
/// method to optimize compression and choose the best storage format.
///
template <typename PixelType>
void WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const;
};
} // namespace io
} // namespace vtkm
#include <vtkm/io/ImageWriter.hxx>
#endif

173
vtkm/io/ImageWriter.hxx Normal file

@ -0,0 +1,173 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_ImageWriter_hxx
#define vtk_m_io_ImageWriter_hxx
#include <vtkm/cont/CellSetStructured.h>
#include <vtkm/cont/DataSetBuilderUniform.h>
#include <vtkm/cont/DataSetFieldAdd.h>
#include <vtkm/io/ImageWriter.h>
#include <vtkm/io/PixelTypes.h>
VTKM_THIRDPARTY_PRE_INCLUDE
#include <vtkm/thirdparty/lodepng/vtkmlodepng/lodepng.h>
VTKM_THIRDPARTY_POST_INCLUDE
namespace vtkm
{
namespace io
{
VTKM_CONT
vtkm::Id BaseImageWriter::GetImageWidth(vtkm::cont::DataSet dataSet) const
{
if (dataSet.GetNumberOfCoordinateSystems() > 0)
{
// Add 1 since the Bounds are 0 indexed
return static_cast<vtkm::Id>(dataSet.GetCoordinateSystem().GetBounds().X.Max) + 1;
}
return 0;
}
VTKM_CONT
vtkm::Id BaseImageWriter::GetImageHeight(vtkm::cont::DataSet dataSet) const
{
if (dataSet.GetNumberOfCoordinateSystems() > 0)
{
// Add 1 since the Bounds are 0 indexed
return static_cast<vtkm::Id>(dataSet.GetCoordinateSystem().GetBounds().Y.Max) + 1;
}
return 0;
}
VTKM_CONT
void PNMWriter::WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const
{
if (!dataSet.HasField(this->PointFieldName))
{
throw vtkm::cont::ErrorBadValue(
"No pixel data found in DataSet, cannot write without image data!");
}
std::ofstream outStream(fileName.c_str(), std::ios_base::binary | std::ios_base::out);
outStream << this->MagicNumber << std::endl
<< this->GetImageWidth(dataSet) << " " << this->GetImageHeight(dataSet) << std::endl;
switch (this->MaxColorValue)
{
case 0:
this->EncodeFile<RGBPixel_8>(outStream, dataSet);
break;
case 255:
this->EncodeFile<RGBPixel_8>(outStream, dataSet);
break;
case 65535:
this->EncodeFile<RGBPixel_16>(outStream, dataSet);
break;
default:
throw vtkm::cont::ErrorBadValue("MaxColorValue: " + std::to_string(this->MaxColorValue) +
" was not one of: {255, 65535}");
}
}
VTKM_CONT
template <typename PixelType>
void PNMWriter::EncodeFile(std::ofstream& outStream, const vtkm::cont::DataSet& dataSet) const
{
outStream << PixelType::MAX_COLOR_VALUE << std::endl;
auto pixelField = dataSet.GetPointField(this->PointFieldName)
.GetData()
.template Cast<vtkm::cont::ArrayHandle<vtkm::Vec4f_32>>();
auto pixelPortal = pixelField.ReadPortal();
vtkm::UInt32 imageSize =
static_cast<vtkm::UInt32>(pixelField.GetNumberOfValues() * PixelType::BYTES_PER_PIXEL);
std::vector<unsigned char> imageData(imageSize);
// Write out the data starting from the end (Images are stored Bottom-Left to Top-Right,
// but are viewed from Top-Left to Bottom-Right)
vtkm::Id imageIndex = 0;
vtkm::Id imageHeight = this->GetImageHeight(dataSet);
vtkm::Id imageWidth = this->GetImageWidth(dataSet);
for (vtkm::Id yIndex = imageHeight - 1; yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < imageWidth; xIndex++, imageIndex++)
{
vtkm::Id index = yIndex * imageWidth + xIndex;
PixelType(pixelPortal.Get(index)).FillImageAtIndexWithPixel(imageData.data(), imageIndex);
}
}
outStream.write((char*)imageData.data(), imageSize);
outStream.close();
}
VTKM_CONT
void PNGWriter::WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const
{
switch (this->MaxColorValue)
{
case 0:
WriteToFile<RGBPixel_8>(fileName, dataSet);
break;
case 255:
WriteToFile<RGBPixel_8>(fileName, dataSet);
break;
case 65535:
WriteToFile<RGBPixel_16>(fileName, dataSet);
break;
default:
throw vtkm::cont::ErrorBadValue("MaxColorValue: " + std::to_string(this->MaxColorValue) +
" was not one of: {255, 65535}");
}
}
VTKM_CONT
template <typename PixelType>
void PNGWriter::WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const
{
if (!dataSet.HasField(this->PointFieldName))
{
throw vtkm::cont::ErrorBadValue(
"No pixel data found in DataSet, cannot write without image data!");
}
auto pixelField = dataSet.GetPointField(this->PointFieldName)
.GetData()
.template Cast<vtkm::cont::ArrayHandle<vtkm::Vec4f_32>>();
auto pixelPortal = pixelField.ReadPortal();
std::vector<unsigned char> imageData(static_cast<typename std::vector<unsigned char>::size_type>(
pixelField.GetNumberOfValues() * PixelType::BYTES_PER_PIXEL));
// Write out the data starting from the end (Images are stored Bottom-Left to Top-Right,
// but are viewed from Top-Left to Bottom-Right)
vtkm::Id imageIndex = 0;
vtkm::Id imageHeight = this->GetImageHeight(dataSet);
vtkm::Id imageWidth = this->GetImageWidth(dataSet);
for (vtkm::Id yIndex = imageHeight - 1; yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < imageWidth; xIndex++, imageIndex++)
{
vtkm::Id index = yIndex * imageWidth + xIndex;
PixelType(pixelPortal.Get(index)).FillImageAtIndexWithPixel(imageData.data(), imageIndex);
}
}
vtkm::png::lodepng_encode_file(fileName.c_str(),
imageData.data(),
static_cast<unsigned>(imageWidth),
static_cast<unsigned>(imageHeight),
PixelType::PNG_COLOR_TYPE,
PixelType::BIT_DEPTH);
}
} // namespace io
} // namespace vtkm
#endif

195
vtkm/io/PixelTypes.h Normal file

@ -0,0 +1,195 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_PixelTypes_h
#define vtk_m_io_PixelTypes_h
#include <vtkm/Types.h>
#include <vtkm/VecTraits.h>
VTKM_THIRDPARTY_PRE_INCLUDE
#include <vtkm/thirdparty/lodepng/vtkmlodepng/lodepng.h>
VTKM_THIRDPARTY_POST_INCLUDE
namespace vtkm
{
namespace io
{
// ----------------------------------------------------------------------
// Define custom SFINAE structures to calculate the VTKM types associated
// with provided BitDepths
template <const vtkm::Id size, typename = void>
struct ComponentTypeFromSize
{
};
template <const vtkm::Id size>
struct ComponentTypeFromSize<size, typename std::enable_if<(size == 8)>::type>
{
using type = vtkm::UInt8;
};
template <const vtkm::Id size>
struct ComponentTypeFromSize<size, typename std::enable_if<(size == 16)>::type>
{
using type = vtkm::UInt16;
};
// ----------------------------------------------------------------------
/// \brief Base type for more complex pixels (RGB, Greyscale, etc) that describes various values
/// such as bit-depth, channel width, bytes per pixel, and how various data should be polled
///
/// \c BasePixel takes BitDepth and Channels as template parameters. BitDepth describes the number
/// of bits in the pixel, while Channels describes the multiple of bits that are available.
/// BasePixel extends vtkm::Vec. The ComponentType is pulled from the ComponentTypeFromSize
/// SFINAE struct defined above. This helps with optimizing the pixel size for a given
/// bit-depth. The Size is pulled from the Channels param.
///
/// \c BasePixel requires BitDepths that are > 8 and powers of 2 at the moment. BitDepths of
/// 4, 2, or 1 bit are not correctly handled at the moment.
///
/// \c BasePixel describes how to populate itself from an unsigned char pointer (assuming that
/// the data stored within the pointer matches the bit-depth and channels described by the
/// BasePixel type), and how to fill in data for an unsigned char pointer. This is mostly
/// useful in serialization and deserialization to various image formats.
///
template <const vtkm::Id BitDepth, const vtkm::IdComponent Channels>
class BasePixel : public vtkm::Vec<typename ComponentTypeFromSize<BitDepth>::type, Channels>
{
static_assert(BitDepth >= 8, "BitDepth not >= 8");
static_assert(!(BitDepth & (BitDepth - 1)), "BitDepth not a power of 2");
public:
using Superclass = vtkm::Vec<typename ComponentTypeFromSize<BitDepth>::type, Channels>;
using ComponentType = typename Superclass::ComponentType;
using BaseType = BasePixel<BitDepth, Channels>;
static constexpr vtkm::IdComponent BIT_DEPTH = BitDepth;
static constexpr vtkm::IdComponent NUM_BYTES = BitDepth / 8;
static constexpr vtkm::IdComponent MAX_COLOR_VALUE = (1 << BitDepth) - 1;
static constexpr vtkm::IdComponent NUM_CHANNELS = Superclass::NUM_COMPONENTS;
static constexpr vtkm::IdComponent BYTES_PER_PIXEL = NUM_CHANNELS * NUM_BYTES;
using Superclass::Superclass;
BasePixel() = default;
/// Fills in this->Components by calling ConstructPixelFromImage. Requires
/// the base vec values to be zeroed out initially.
///
BasePixel(const unsigned char* imageData, const vtkm::Id index)
: Superclass(0)
{
ConstructPixelFromImage(imageData, index);
}
/// Calculates this difference between two pixels as a single value.
///
virtual ComponentType Diff(const BaseType& pixel) const = 0;
/// Generates a Vec4f_32 from the current data available in the pixel
///
virtual vtkm::Vec4f_32 ToVec4f() const = 0;
/// Implement the << operator for this class type. Will call the overloaded
/// print method for the subclassed type.
///
friend std::ostream& operator<<(std::ostream& os, const BaseType& basePixel)
{
basePixel.print(os);
return os;
}
/// Takes an output imageData pointer and in index to a location in that dataset
/// and fills in the pixel data at the location. Utilizes BIT_DEPTH and
/// NUM_CHANNELS to fill in multiple bytes worth of data if necessary.
///
void FillImageAtIndexWithPixel(unsigned char* imageData, const vtkm::Id index);
protected:
/// Takes an input imageData pointer and an index to a location in that dataset
/// and fills in this->Components correctly using the provided BIT_DEPTH and
/// NUM_CHANNELS. Does NOT 0 out the current Components.
///
void ConstructPixelFromImage(const unsigned char* imageData, const vtkm::Id index);
virtual void print(std::ostream& os) const = 0;
};
template <const vtkm::Id BitDepth>
class RGBPixel : public BasePixel<BitDepth, 3>
{
public:
// RGB values are stored in a vtkm::Vec<ComponentType, 3>
using Superclass = BasePixel<BitDepth, 3>;
using ComponentType = typename Superclass::ComponentType;
static constexpr vtkm::png::LodePNGColorType PNG_COLOR_TYPE =
vtkm::png::LodePNGColorType::LCT_RGB;
using Superclass::Superclass;
RGBPixel() = default;
RGBPixel(vtkm::Vec4f_32 tuple)
: Superclass(static_cast<ComponentType>(tuple[0] * this->MAX_COLOR_VALUE),
static_cast<ComponentType>(tuple[1] * this->MAX_COLOR_VALUE),
static_cast<ComponentType>(tuple[2] * this->MAX_COLOR_VALUE))
{
}
ComponentType Diff(const Superclass& pixel) const override;
vtkm::Vec4f_32 ToVec4f() const override;
protected:
void print(std::ostream& os) const override
{
os << "(" << (int)this->Components[0] << "," << (int)this->Components[1] << ","
<< (int)this->Components[2] << ")";
}
};
// Default types for 8 and 16 bit RGB pixels
using RGBPixel_8 = RGBPixel<8>;
using RGBPixel_16 = RGBPixel<16>;
template <const vtkm::Id BitDepth>
class GreyPixel : public BasePixel<BitDepth, 1>
{
public:
// Grey values are stored in a vtkm::Vec<ComponentType, 1>
// Note: A vec of size 1 is used instead of just a `ComponentType`
// in order to simplify the pixel helper functions
using Superclass = BasePixel<BitDepth, 1>;
using ComponentType = typename Superclass::ComponentType;
static constexpr vtkm::png::LodePNGColorType PNG_COLOR_TYPE =
vtkm::png::LodePNGColorType::LCT_GREY;
using Superclass::Superclass;
GreyPixel() = default;
GreyPixel(vtkm::Vec4f_32 tuple)
: Superclass(
static_cast<ComponentType>((tuple[0] + tuple[1] + tuple[2]) * this->MAX_COLOR_VALUE / 3))
{
}
ComponentType Diff(const Superclass& pixel) const override;
vtkm::Vec4f_32 ToVec4f() const override;
protected:
void print(std::ostream& os) const override { os << "(" << (int)this->Components[0] << ")"; }
};
// Default types for 8 and 16 bit Grey pixels
using GreyPixel_16 = GreyPixel<16>;
using GreyPixel_8 = GreyPixel<8>;
} // namespace io
} // namespace vtkm
#include <vtkm/io/PixelTypes.hxx>
#endif //vtk_m_io_PixelTypes_h

85
vtkm/io/PixelTypes.hxx Normal file

@ -0,0 +1,85 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_io_PixelTypes_hxx
#define vtk_m_io_PixelTypes_hxx
#include <vtkm/Math.h>
#include <vtkm/io/PixelTypes.h>
namespace vtkm
{
namespace io
{
template <const vtkm::Id B, const vtkm::IdComponent C>
void BasePixel<B, C>::FillImageAtIndexWithPixel(unsigned char* imageData, const vtkm::Id index)
{
vtkm::Id initShift = BIT_DEPTH - 8;
for (vtkm::Id channel = 0; channel < NUM_CHANNELS; channel++)
{
for (vtkm::Id shift = initShift, i = 0; shift >= 0; shift -= 8, i++)
{
imageData[index * BYTES_PER_PIXEL + i + (channel * NUM_BYTES)] =
static_cast<unsigned char>((this->Components[channel] & (0xff << shift)) >> shift);
}
}
}
template <const vtkm::Id B, const vtkm::IdComponent C>
void BasePixel<B, C>::ConstructPixelFromImage(const unsigned char* imageData, const vtkm::Id index)
{
vtkm::Id initShift = BIT_DEPTH - 8;
for (vtkm::Id channel = 0; channel < NUM_CHANNELS; channel++)
{
for (vtkm::Id shift = initShift, i = 0; shift >= 0; shift -= 8, i++)
{
this->Components[channel] |= imageData[index * BYTES_PER_PIXEL + i + (channel * NUM_BYTES)]
<< shift;
}
}
}
template <const vtkm::Id B>
typename RGBPixel<B>::ComponentType RGBPixel<B>::Diff(const Superclass& pixel) const
{
return static_cast<RGBPixel<B>::ComponentType>(vtkm::Abs(this->Components[0] - pixel[0]) +
vtkm::Abs(this->Components[1] - pixel[1]) +
vtkm::Abs(this->Components[2] - pixel[2]));
}
template <const vtkm::Id B>
vtkm::Vec4f_32 RGBPixel<B>::ToVec4f() const
{
return vtkm::Vec4f_32(static_cast<vtkm::Float32>(this->Components[0]) / this->MAX_COLOR_VALUE,
static_cast<vtkm::Float32>(this->Components[1]) / this->MAX_COLOR_VALUE,
static_cast<vtkm::Float32>(this->Components[2]) / this->MAX_COLOR_VALUE,
1);
}
template <const vtkm::Id B>
typename GreyPixel<B>::ComponentType GreyPixel<B>::Diff(const Superclass& pixel) const
{
return static_cast<GreyPixel<B>::ComponentType>(vtkm::Abs(this->Components[0] - pixel[0]));
}
template <const vtkm::Id B>
vtkm::Vec4f_32 GreyPixel<B>::ToVec4f() const
{
return vtkm::Vec4f_32(static_cast<vtkm::Float32>(this->Components[0]) / this->MAX_COLOR_VALUE,
static_cast<vtkm::Float32>(this->Components[0]) / this->MAX_COLOR_VALUE,
static_cast<vtkm::Float32>(this->Components[0]) / this->MAX_COLOR_VALUE,
1);
}
} // namespace io
} // namespace vtkm
#endif //vtk_m_io_PixelTypes_h

@ -13,4 +13,6 @@ set(headers
VTKDataSetReader.h
)
vtkm_declare_headers(${headers})
vtkm_declare_headers(
${headers}
)

@ -10,8 +10,19 @@
set(unit_tests
UnitTestBOVDataSetReader.cxx
UnitTestPixelTypes.cxx
UnitTestVTKDataSetReader.cxx
UnitTestVTKDataSetWriter.cxx
)
)
vtkm_unit_tests(SOURCES ${unit_tests} ALL_BACKENDS)
vtkm_unit_tests(SOURCES ${unit_tests} ALL_BACKENDS LIBRARIES vtkm_lodepng)
if(NOT VTKm_ENABLE_RENDERING)
return()
endif()
set(image_unit_tests
UnitTestImageWriter.cxx
)
vtkm_unit_tests(NAME UnitTests_vtkm_io_image_testing SOURCES ${image_unit_tests} ALL_BACKENDS LIBRARIES vtkm_rendering vtkm_lodepng)

@ -0,0 +1,166 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/io/ImageReader.h>
#include <vtkm/io/ImageWriter.h>
#include <vtkm/io/PixelTypes.h>
#include <vtkm/rendering/Canvas.h>
#include <vtkm/rendering/Color.h>
#include <string>
namespace
{
using namespace vtkm::io;
using namespace vtkm::rendering;
template <typename PixelType>
void TestFilledImage(vtkm::cont::DataSet& dataSet,
const std::string& fieldName,
const vtkm::rendering::Canvas& canvas)
{
VTKM_TEST_ASSERT(dataSet.HasPointField(fieldName), "Point Field Not Found: " + fieldName);
auto pointField = dataSet.GetPointField(fieldName);
VTKM_TEST_ASSERT(pointField.GetNumberOfValues() == canvas.GetWidth() * canvas.GetHeight(),
"wrong image dimensions");
VTKM_TEST_ASSERT(pointField.GetData().template IsType<vtkm::cont::ArrayHandle<vtkm::Vec4f_32>>(),
"wrong ArrayHandle type");
auto pixelPortal =
pointField.GetData().template Cast<vtkm::cont::ArrayHandle<vtkm::Vec4f_32>>().ReadPortal();
auto colorPortal = canvas.GetColorBuffer().ReadPortal();
for (vtkm::Id y = 0; y < canvas.GetHeight(); y++)
{
std::ostringstream row;
row << "[";
for (vtkm::Id x = 0; x < canvas.GetWidth(); x++)
{
auto tuple = colorPortal.Get(y * canvas.GetWidth() + x);
auto pixelVec = PixelType(pixelPortal.Get(y * canvas.GetWidth() + x));
std::ostringstream data;
data << pixelVec << ":" << PixelType(tuple) << std::endl;
VTKM_TEST_ASSERT(pixelVec == PixelType(tuple),
"Stored pixel did not match canvas value" + data.str());
row << pixelVec << ",";
}
row << "]";
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Row[" << y << "]" << row.str());
}
}
template <typename PixelType>
void TestCreateImageDataSet(BaseImageReader& reader, const vtkm::rendering::Canvas& canvas)
{
auto dataSet = reader.CreateImageDataSet(canvas);
TestFilledImage<PixelType>(dataSet, reader.GetPointFieldName(), canvas);
}
template <typename PixelType>
void TestReadAndWritePNG(const vtkm::rendering::Canvas& canvas, std::string image)
{
auto pngReader = PNGReader();
auto pngWriter = PNGWriter();
vtkm::cont::DataSet dataSet;
bool throws = false;
try
{
pngWriter.WriteToFile(image, dataSet);
}
catch (const vtkm::cont::ErrorBadValue&)
{
throws = true;
}
VTKM_TEST_ASSERT(throws, "Fill Image did not throw with empty data");
dataSet = pngReader.CreateImageDataSet(canvas);
pngWriter.WriteToFile(image, dataSet);
dataSet = pngReader.ReadFromFile(image);
pngWriter.WriteToFile(image, dataSet);
dataSet = pngReader.ReadFromFile(image);
TestFilledImage<PixelType>(dataSet, pngReader.GetPointFieldName(), canvas);
}
template <const vtkm::Id BitDepth>
void TestReadAndWritePNM(const vtkm::rendering::Canvas& canvas)
{
using PixelType = RGBPixel<BitDepth>;
PNMWriter ppmWriter((1 << BitDepth) - 1);
PNMReader ppmReader((1 << BitDepth) - 1);
vtkm::cont::DataSet dataSet;
bool throws = false;
try
{
ppmWriter.WriteToFile("ppmTestFile" + std::to_string(BitDepth) + "bit.ppm", dataSet);
}
catch (const vtkm::cont::ErrorBadValue&)
{
throws = true;
}
VTKM_TEST_ASSERT(throws, "Fill Image did not throw with empty data");
dataSet = ppmReader.CreateImageDataSet(canvas);
ppmWriter.WriteToFile("ppmTestFile.ppm", dataSet);
dataSet = ppmReader.ReadFromFile("ppmTestFile.ppm");
ppmWriter.WriteToFile("ppmTestFile2.ppm", dataSet);
dataSet = ppmReader.ReadFromFile("ppmTestFile2.ppm");
TestFilledImage<PixelType>(dataSet, ppmReader.GetPointFieldName(), canvas);
}
void TestBaseImageMethods(const vtkm::rendering::Canvas& canvas)
{
auto reader = PNGReader();
TestCreateImageDataSet<RGBPixel_8>(reader, canvas);
TestCreateImageDataSet<RGBPixel_16>(reader, canvas);
TestCreateImageDataSet<GreyPixel_8>(reader, canvas);
TestCreateImageDataSet<GreyPixel_16>(reader, canvas);
}
void TestPNMImage(const vtkm::rendering::Canvas& canvas)
{
TestReadAndWritePNM<8>(canvas);
TestReadAndWritePNM<16>(canvas);
}
void TestPNGImage(const vtkm::rendering::Canvas& canvas)
{
TestReadAndWritePNG<RGBPixel_8>(canvas, "pngRGB8Test.png");
TestReadAndWritePNG<RGBPixel_16>(canvas, "pngRGB16Test.png");
TestReadAndWritePNG<GreyPixel_8>(canvas, "pngGrey8Test.png");
TestReadAndWritePNG<GreyPixel_16>(canvas, "pngGrey16Test.png");
}
void TestImage()
{
vtkm::rendering::Canvas canvas(16, 16);
canvas.SetBackgroundColor(vtkm::rendering::Color::red);
canvas.Initialize();
canvas.Activate();
canvas.Clear();
// Line from top left to bottom right, ensures correct transposedness
canvas.AddLine(-0.9, 0.9, 0.9, -0.9, 2.0f, vtkm::rendering::Color::black);
vtkm::Bounds colorBarBounds(-0.8, -0.6, -0.8, 0.8, 0, 0);
canvas.AddColorBar(colorBarBounds, vtkm::cont::ColorTable("inferno"), false);
canvas.BlendBackground();
canvas.SaveAs("baseline.ppm");
TestBaseImageMethods(canvas);
TestPNMImage(canvas);
TestPNGImage(canvas);
}
}
int UnitTestImageWriter(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(TestImage, argc, argv);
}

@ -0,0 +1,176 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/io/PixelTypes.h>
#include <string>
using namespace vtkm::io;
template <typename PixelType>
void TestPixelTypeOperations(const vtkm::UInt16& numPixels = 10)
{
using ValType = typename PixelType::ComponentType;
const ValType numBytes = static_cast<ValType>(PixelType::NUM_BYTES);
const ValType numChannels = static_cast<ValType>(PixelType::NUM_CHANNELS);
// Fill in the imageData through FillPixelData
std::vector<unsigned char> imageData(numPixels * numBytes * numChannels);
std::vector<PixelType> pixelVector(numPixels);
for (ValType i = 0; i < numPixels; i++)
{
ValType pixelVal = 0;
for (ValType j = 0, shift = numBytes - 1; j < numBytes; shift--, j++)
{
pixelVal += (i + j) << (shift * 8);
}
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "pixelVal[" << i << "] = " << pixelVal);
PixelType pixel(pixelVal);
pixelVector[i] = pixel;
pixel.FillImageAtIndexWithPixel(imageData.data(), i);
}
// Test that the imageData values were set correctly
VTKM_TEST_ASSERT(static_cast<vtkm::Id>(imageData.size()) == numPixels * numChannels * numBytes,
"Wrong number of elements");
for (ValType j = 0; j < numBytes; j++)
{
for (ValType i = 0; i < numPixels; i++)
{
for (ValType k = numChannels * i; k < numChannels * i + numChannels; k++)
{
VTKM_TEST_ASSERT(imageData[k * numBytes + j] == i + j,
"Wrong value at index[" + std::to_string(k * numBytes + j) + "]: " +
std::to_string(imageData[k * numBytes + j]) + " != " +
std::to_string(i + j));
}
}
}
// Test that a pixel can be retreived from the filled out data vector
for (vtkm::Id i = 0; i < numPixels; i++)
{
VTKM_TEST_ASSERT(pixelVector[static_cast<typename std::vector<PixelType>::size_type>(i)] ==
PixelType(imageData.data(), i),
"Incorrect pixel value");
}
}
void TestDifferentPixelTypes()
{
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing 8 bit RGB");
TestPixelTypeOperations<RGBPixel_8>();
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing 8 bit Grey");
TestPixelTypeOperations<GreyPixel_8>();
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing 16 bit RGB");
TestPixelTypeOperations<RGBPixel_16>();
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing 16 bit Grey");
TestPixelTypeOperations<GreyPixel_16>();
}
void TestGreyPixelConstructors()
{
std::vector<unsigned char> initData{ 1, 2 };
auto pixel_1 = GreyPixel_8(1);
auto pixel_2 = GreyPixel_8(1);
auto pixel_3 = GreyPixel_8(2);
auto pixel_4 = GreyPixel_8(initData.data(), 0);
auto pixel_5 = GreyPixel_8(initData.data(), 1);
auto pixel_6 = GreyPixel_16(initData.data(), 0);
float color = 10.0f / GreyPixel_16::MAX_COLOR_VALUE;
auto pixel_7 = GreyPixel_16({ color, color, color, 5 });
VTKM_TEST_ASSERT(vtkm::UInt16(1) == pixel_1[0], "Type mis-match");
VTKM_TEST_ASSERT(vtkm::FloatDefault(0) == pixel_1.Diff(pixel_2), "Incorrect Diff");
VTKM_TEST_ASSERT(vtkm::FloatDefault(1) == pixel_1.Diff(pixel_3), "Incorrect Diff");
VTKM_TEST_ASSERT(vtkm::Vec4f_32(1.0f / 255, 1.0f / 255, 1.0f / 255, 1) == pixel_1.ToVec4f(),
"Incorrect Conversion");
VTKM_TEST_ASSERT(vtkm::Vec<vtkm::UInt8, 1>(1) == pixel_4, "Bad 1st value 8 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec<vtkm::UInt8, 1>(2) == pixel_5, "Bad 2nd value 8 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec<vtkm::UInt16, 1>(258) == pixel_6, "Bad 16 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec4f_32(258.0f / 65535, 258.0f / 65535, 258.0f / 65535, 1) ==
pixel_6.ToVec4f(),
"Incorrect Conversion");
VTKM_TEST_ASSERT(vtkm::Vec<vtkm::UInt16, 1>(10) == pixel_7, "Bad Vec4f_32 construction");
VTKM_TEST_ASSERT(GreyPixel<16>::BIT_DEPTH == 16, "Bad BitDepth");
VTKM_TEST_ASSERT(GreyPixel<16>::NUM_BYTES == 2, "Bad NumBytes");
VTKM_TEST_ASSERT(GreyPixel<16>::MAX_COLOR_VALUE == 65535, "Bad NumBytes");
VTKM_TEST_ASSERT(GreyPixel<16>::NUM_CHANNELS == 1, "Bad NumChannels");
VTKM_TEST_ASSERT(GreyPixel<16>::BYTES_PER_PIXEL == 2, "Wrong Pixel Byte distance");
// Shouldn't compile
// auto pixel_4 = RGBPixel_8(1, 1, 1);
// pixel_1.Diff(pixel_4);
}
void TestRGBPixelConstructors()
{
std::vector<unsigned char> initData{ 1, 2, 3, 4, 5, 6 };
auto pixel_1 = RGBPixel_8(1, 1, 1);
auto pixel_2 = RGBPixel_8(1, 1, 1);
auto pixel_3 = RGBPixel_8(1);
auto pixel_4 = RGBPixel_8(2, 2, 2);
auto pixel_5 = RGBPixel_8(initData.data(), 0);
auto pixel_6 = RGBPixel_8(initData.data(), 1);
auto pixel_7 = RGBPixel_16(initData.data(), 0);
float color = 10.0f / RGBPixel_16::MAX_COLOR_VALUE;
auto pixel_8 = RGBPixel_16({ color, color, color, 5 });
VTKM_TEST_ASSERT(vtkm::Vec3ui_8(1, 1, 1) == pixel_1, "Type mis-match");
VTKM_TEST_ASSERT(vtkm::FloatDefault(0) == pixel_1.Diff(pixel_2), "Incorrect Diff");
VTKM_TEST_ASSERT(vtkm::FloatDefault(0) == pixel_1.Diff(pixel_3), "Incorrect Diff");
VTKM_TEST_ASSERT(vtkm::FloatDefault(3) == pixel_1.Diff(pixel_4), "Incorrect Diff");
VTKM_TEST_ASSERT(vtkm::Vec4f_32(1.0f / 255, 1.0f / 255, 1.0f / 255, 1) == pixel_1.ToVec4f(),
"Incorrect Conversion");
VTKM_TEST_ASSERT(vtkm::Vec3ui_8(1, 2, 3) == pixel_5, "Bad 1st value 8 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec3ui_8(4, 5, 6) == pixel_6, "Bad 2nd value 8 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec3ui_16(258, 772, 1286) == pixel_7, "Bad 16 bit construct");
VTKM_TEST_ASSERT(vtkm::Vec4f_32(258.0f / 65535, 772.0f / 65535, 1286.0f / 65535, 1) ==
pixel_7.ToVec4f(),
"Incorrect Conversion");
VTKM_TEST_ASSERT(vtkm::Vec<vtkm::UInt16, 3>(10, 10, 10) == pixel_8, "Bad Vec4f_32 construction");
VTKM_TEST_ASSERT(RGBPixel<16>::BIT_DEPTH == 16, "Bad BitDepth");
VTKM_TEST_ASSERT(RGBPixel<16>::NUM_BYTES == 2, "Bad NumBytes");
VTKM_TEST_ASSERT(RGBPixel<16>::MAX_COLOR_VALUE == 65535, "Bad NumBytes");
VTKM_TEST_ASSERT(RGBPixel<16>::NUM_CHANNELS == 3, "Bad NumChannels");
VTKM_TEST_ASSERT(RGBPixel<16>::BYTES_PER_PIXEL == 6, "Wrong Pixel Byte distance");
// Shouldn't compile
// auto pixel_8 = GreyPixel_8(1);
// pixel_1.Diff(pixel_8);
}
void TestPixelTypes()
{
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing RGBPixel");
TestRGBPixelConstructors();
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing GreyPixel");
TestGreyPixelConstructors();
VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Testing Pixel Types");
TestDifferentPixelTypes();
}
int UnitTestPixelTypes(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(TestPixelTypes, argc, argv);
}

@ -10,6 +10,8 @@
set(headers
VTKDataSetWriter.h
)
)
vtkm_declare_headers(${headers})
vtkm_declare_headers(
${headers}
)