mirror of
https://gitlab.kitware.com/vtk/vtk-m
synced 2024-10-05 01:49:02 +00:00
Implement and test ImageReader and ImageWriter capabilities in the io library
This commit is contained in:
parent
ba77f36347
commit
24d022b02b
32
docs/changelog/image_io.md
Normal file
32
docs/changelog/image_io.md
Normal file
@ -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
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
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
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
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
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
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)
|
||||
|
166
vtkm/io/testing/UnitTestImageWriter.cxx
Normal file
166
vtkm/io/testing/UnitTestImageWriter.cxx
Normal file
@ -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);
|
||||
}
|
176
vtkm/io/testing/UnitTestPixelTypes.cxx
Normal file
176
vtkm/io/testing/UnitTestPixelTypes.cxx
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.
|
||||
//============================================================================
|
||||
|
||||
#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}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user