Update image writes to style and library

Updated the image writer classes to better follow VTK-m naming
convention (i.e. use `ImageWriterPNG` instead of `PNGWriter`). The
structure of the image writer class now also better matches the
interface for the VTK writer.

Also put the implementation of the image writers into the `vtkm_io`
library so that it only has to be compiled once.
This commit is contained in:
Kenneth Moreland 2020-05-28 19:00:33 -06:00
parent 1e91b32ace
commit 6c97054b7f
10 changed files with 464 additions and 378 deletions

@ -16,7 +16,9 @@ set(headers
ImageReaderBase.h
ImageReaderPNG.h
ImageReaderPNM.h
ImageWriter.h
ImageWriterBase.h
ImageWriterPNG.h
ImageWriterPNM.h
PixelTypes.h
VTKDataSetReader.h
VTKDataSetReaderBase.h
@ -29,7 +31,6 @@ set(headers
)
set(template_sources
ImageWriter.hxx
PixelTypes.hxx
)
@ -47,6 +48,9 @@ set(device_sources
ImageReaderBase.cxx
ImageReaderPNG.cxx
ImageReaderPNM.cxx
ImageWriterBase.cxx
ImageWriterPNG.cxx
ImageWriterPNM.cxx
VTKDataSetReader.cxx
VTKDataSetReaderBase.cxx
VTKDataSetWriter.cxx

@ -1,140 +0,0 @@
//============================================================================
// 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

@ -1,172 +0,0 @@
//============================================================================
// 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/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

@ -0,0 +1,88 @@
//============================================================================
// 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/io/ImageWriterBase.h>
#include <vtkm/cont/Logging.h>
namespace vtkm
{
namespace io
{
ImageWriterBase::ImageWriterBase(const char* filename)
: FileName(filename)
{
}
ImageWriterBase::ImageWriterBase(const std::string& filename)
: FileName(filename)
{
}
ImageWriterBase::~ImageWriterBase() noexcept
{
}
void ImageWriterBase::WriteDataSet(const vtkm::cont::DataSet& dataSet)
{
this->WriteDataSet(dataSet, std::string{});
}
void ImageWriterBase::WriteDataSet(const vtkm::cont::DataSet& dataSet,
const std::string& colorFieldName)
{
using CellSetType = vtkm::cont::CellSetStructured<2>;
if (!dataSet.GetCellSet().IsType<CellSetType>())
{
throw vtkm::cont::ErrorBadType(
"Image writers can only write data sets with 2D structured data.");
}
CellSetType cellSet = dataSet.GetCellSet().Cast<CellSetType>();
vtkm::Id2 cellDimensions = cellSet.GetCellDimensions();
// Number of points is one more in each dimension than number of cells
vtkm::Id width = cellDimensions[0] + 1;
vtkm::Id height = cellDimensions[1] + 1;
vtkm::cont::Field colorField;
if (!colorFieldName.empty())
{
if (!dataSet.HasPointField(colorFieldName))
{
throw vtkm::cont::ErrorBadValue("Data set does not have requested field " + colorFieldName);
}
colorField = dataSet.GetPointField(colorFieldName);
}
else
{
// Find a field of the correct type.
vtkm::Id numFields = dataSet.GetNumberOfFields();
bool foundField = false;
for (vtkm::Id fieldId = 0; fieldId < numFields; ++fieldId)
{
colorField = dataSet.GetField(fieldId);
if ((colorField.GetAssociation() == vtkm::cont::Field::Association::POINTS) &&
(colorField.GetData().IsType<ColorArrayType>()))
{
foundField = true;
break;
}
}
if (!foundField)
{
throw vtkm::cont::ErrorBadValue(
"Data set does not have any fields that look like color data.");
}
}
this->Write(width, height, colorField.GetData().Cast<ColorArrayType>());
}
}
} // namespace vtkm::io

81
vtkm/io/ImageWriterBase.h Normal file

@ -0,0 +1,81 @@
//============================================================================
// 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_ImageWriterBase_h
#define vtk_m_io_ImageWriterBase_h
#include <vtkm/cont/DataSet.h>
#include <vtkm/io/vtkm_io_export.h>
namespace vtkm
{
namespace io
{
/// \brief Manages writing, and loading data from images
///
/// `ImageWriterBase` 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.
///
/// `ImageWriterBase` can be constructed from a file, canvas, or ArrayHandle. It can
/// also be empy constructed and filled in with a dataset later.
///
/// `ImageWriterBase` 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 VTKM_IO_EXPORT ImageWriterBase
{
public:
using ColorArrayType = vtkm::cont::ArrayHandle<vtkm::Vec4f_32>;
VTKM_CONT ImageWriterBase(const char* filename);
VTKM_CONT ImageWriterBase(const std::string& filename);
VTKM_CONT virtual ~ImageWriterBase() noexcept;
ImageWriterBase(const ImageWriterBase&) = delete;
ImageWriterBase& operator=(const ImageWriterBase&) = delete;
///@{
/// \brief Write the color field of a data set to an image file.
///
/// The `DataSet` must have a 2D structured cell set.
///
/// The specified color field must be of type `ColorArrayType` (a basic
/// `ArrayHandle` of `vtkm::Vec4f_32`). If no color field name is given,
/// the first point field that matches this criteria is written.
///
VTKM_CONT void WriteDataSet(const vtkm::cont::DataSet& dataSet);
VTKM_CONT void WriteDataSet(const vtkm::cont::DataSet& dataSet, const std::string& colorField);
///@}
enum class PixelDepth
{
PIXEL_8,
PIXEL_16
};
///@{
/// You can specify the number of bits used by each color channel with the `PixelDepth`.
///
VTKM_CONT PixelDepth GetPixelDepth() const { return this->Depth; }
VTKM_CONT void SetPixelDepth(PixelDepth depth) { this->Depth = depth; }
///@}
protected:
std::string FileName;
PixelDepth Depth = PixelDepth::PIXEL_8;
VTKM_CONT virtual void Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels) = 0;
};
}
}
#endif //vtk_m_io_ImageWriterBase_h

@ -0,0 +1,69 @@
//============================================================================
// 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/io/ImageWriterPNG.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
{
ImageWriterPNG::~ImageWriterPNG() noexcept
{
}
void ImageWriterPNG::Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
switch (this->Depth)
{
case PixelDepth::PIXEL_8:
this->WriteToFile<vtkm::io::RGBPixel_8>(width, height, pixels);
break;
case PixelDepth::PIXEL_16:
WriteToFile<vtkm::io::RGBPixel_16>(width, height, pixels);
break;
}
}
template <typename PixelType>
void ImageWriterPNG::WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
auto pixelPortal = pixels.ReadPortal();
std::vector<unsigned char> imageData(static_cast<typename std::vector<unsigned char>::size_type>(
pixels.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 pngIndex = 0;
for (vtkm::Id yIndex = height - 1; yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < width; xIndex++)
{
vtkm::Id vtkmIndex = yIndex * width + xIndex;
PixelType(pixelPortal.Get(vtkmIndex)).FillImageAtIndexWithPixel(imageData.data(), pngIndex);
pngIndex++;
}
}
vtkm::png::lodepng_encode_file(this->FileName.c_str(),
imageData.data(),
static_cast<unsigned>(width),
static_cast<unsigned>(height),
PixelType::PNG_COLOR_TYPE,
PixelType::BIT_DEPTH);
}
}
} // namespace vtkm::io

46
vtkm/io/ImageWriterPNG.h Normal file

@ -0,0 +1,46 @@
//============================================================================
// 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_ImageWriterPNG_h
#define vtk_m_io_ImageWriterPNG_h
#include <vtkm/io/ImageWriterBase.h>
namespace vtkm
{
namespace io
{
/// \brief Manages writing images using the PNG format via lodepng
///
/// \c ImageWriterPNG extends vtkm::io::ImageWriterBase 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.
///
class VTKM_IO_EXPORT ImageWriterPNG : public vtkm::io::ImageWriterBase
{
using Superclass = vtkm::io::ImageWriterBase;
public:
using Superclass::Superclass;
VTKM_CONT ~ImageWriterPNG() noexcept override;
ImageWriterPNG(const ImageWriterPNG&) = delete;
ImageWriterPNG& operator=(const ImageWriterPNG&) = delete;
protected:
VTKM_CONT void Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels) override;
template <typename PixelType>
VTKM_CONT void WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels);
};
}
} // namespace vtkm::io
#endif //vtk_m_io_ImageWriterPNG_h

@ -0,0 +1,65 @@
//============================================================================
// 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/io/ImageWriterPNM.h>
#include <vtkm/io/PixelTypes.h>
namespace vtkm
{
namespace io
{
ImageWriterPNM::~ImageWriterPNM() noexcept
{
}
void ImageWriterPNM::Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
switch (this->Depth)
{
case PixelDepth::PIXEL_8:
this->WriteToFile<vtkm::io::RGBPixel_8>(width, height, pixels);
break;
case PixelDepth::PIXEL_16:
WriteToFile<vtkm::io::RGBPixel_16>(width, height, pixels);
break;
}
}
template <typename PixelType>
void ImageWriterPNM::WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels)
{
std::ofstream outStream(this->FileName.c_str(), std::ios_base::binary | std::ios_base::out);
outStream << "P6\n" << width << " " << height << "\n";
outStream << PixelType::MAX_COLOR_VALUE << "\n";
auto pixelPortal = pixels.ReadPortal();
vtkm::UInt32 imageSize =
static_cast<vtkm::UInt32>(pixels.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 pnmIndex = 0;
for (vtkm::Id yIndex = height - 1; yIndex >= 0; yIndex--)
{
for (vtkm::Id xIndex = 0; xIndex < width; xIndex++, pnmIndex++)
{
vtkm::Id vtkmIndex = yIndex * width + xIndex;
PixelType(pixelPortal.Get(vtkmIndex)).FillImageAtIndexWithPixel(imageData.data(), pnmIndex);
}
}
outStream.write((char*)imageData.data(), imageSize);
outStream.close();
}
}
} // namespace vtkm::io

53
vtkm/io/ImageWriterPNM.h Normal file

@ -0,0 +1,53 @@
//============================================================================
// 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_ImageWriterPNM_h
#define vtk_m_io_ImageWriterPNM_h
#include <vtkm/io/ImageWriterBase.h>
namespace vtkm
{
namespace io
{
/// \brief Manages writing images using the PNM format
///
/// `ImageWriterPNM` extends `ImageWriterBase`, 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 VTKM_IO_EXPORT ImageWriterPNM : public vtkm::io::ImageWriterBase
{
using Superclass = vtkm::io::ImageWriterBase;
public:
using Superclass::Superclass;
VTKM_CONT ~ImageWriterPNM() noexcept override;
ImageWriterPNM(const ImageWriterPNM&) = delete;
ImageWriterPNM& operator=(const ImageWriterPNM&) = delete;
/// 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.
///
VTKM_CONT void Write(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels) override;
protected:
template <typename PixelType>
VTKM_CONT void WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels);
};
}
} // namespace vtkm::io
#endif //vtk_m_io_ImageWriterPNM_h

@ -11,7 +11,8 @@
#include <vtkm/cont/testing/Testing.h>
#include <vtkm/io/ImageReaderPNG.h>
#include <vtkm/io/ImageReaderPNM.h>
#include <vtkm/io/ImageWriter.h>
#include <vtkm/io/ImageWriterPNG.h>
#include <vtkm/io/ImageWriterPNM.h>
#include <vtkm/io/PixelTypes.h>
#include <vtkm/rendering/Canvas.h>
#include <vtkm/rendering/Color.h>
@ -24,7 +25,6 @@ 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)
@ -40,119 +40,111 @@ void TestFilledImage(vtkm::cont::DataSet& dataSet,
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_TEST_ASSERT(test_equal_portals(pixelPortal, colorPortal));
}
template <typename PixelType>
void TestCreateImageDataSet(const vtkm::rendering::Canvas& canvas)
{
std::cout << "TestCreateImageDataSet" << std::endl;
auto dataSet = canvas.GetDataSet("pixel-color");
TestFilledImage<PixelType>(dataSet, "pixel-color", canvas);
TestFilledImage(dataSet, "pixel-color", canvas);
}
template <typename PixelType>
void TestReadAndWritePNG(const vtkm::rendering::Canvas& canvas, std::string filename)
void TestReadAndWritePNG(const vtkm::rendering::Canvas& canvas,
std::string filename,
vtkm::io::ImageWriterBase::PixelDepth pixelDepth)
{
auto pngWriter = PNGWriter();
vtkm::cont::DataSet dataSet;
std::cout << "TestReadAndWritePNG - " << filename << std::endl;
bool throws = false;
try
{
pngWriter.WriteToFile(filename, dataSet);
vtkm::io::ImageWriterPNG writer(filename);
vtkm::cont::DataSet dataSet;
writer.WriteDataSet(dataSet);
}
catch (const vtkm::cont::ErrorBadValue&)
catch (const vtkm::cont::Error&)
{
throws = true;
}
VTKM_TEST_ASSERT(throws, "Fill Image did not throw with empty data");
dataSet = canvas.GetDataSet(pngWriter.GetPointFieldName());
pngWriter.WriteToFile(filename, dataSet);
{
vtkm::io::ImageReaderPNG reader(filename);
dataSet = reader.ReadDataSet();
// TODO: Fix this
vtkm::cont::Field field = dataSet.GetField(reader.GetPointFieldName());
dataSet.AddPointField(pngWriter.GetPointFieldName(), field.GetData());
vtkm::io::ImageWriterPNG writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
pngWriter.WriteToFile(filename, dataSet);
{
vtkm::io::ImageReaderPNG reader(filename);
dataSet = reader.ReadDataSet();
TestFilledImage<PixelType>(dataSet, reader.GetPointFieldName(), canvas);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
}
{
vtkm::io::ImageWriterPNG writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
{
vtkm::io::ImageReaderPNG reader(filename);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
TestFilledImage(dataSet, reader.GetPointFieldName(), canvas);
}
}
template <const vtkm::Id BitDepth>
void TestReadAndWritePNM(const vtkm::rendering::Canvas& canvas)
void TestReadAndWritePNM(const vtkm::rendering::Canvas& canvas,
std::string filename,
vtkm::io::ImageWriterBase::PixelDepth pixelDepth)
{
using PixelType = RGBPixel<BitDepth>;
PNMWriter ppmWriter((1 << BitDepth) - 1);
vtkm::cont::DataSet dataSet;
std::cout << "TestReadAndWritePNM - " << filename << std::endl;
bool throws = false;
try
{
ppmWriter.WriteToFile("ppmTestFile" + std::to_string(BitDepth) + "bit.ppm", dataSet);
vtkm::io::ImageWriterPNM writer(filename);
vtkm::cont::DataSet dataSet;
writer.WriteDataSet(dataSet);
}
catch (const vtkm::cont::ErrorBadValue&)
catch (const vtkm::cont::Error&)
{
throws = true;
}
VTKM_TEST_ASSERT(throws, "Fill Image did not throw with empty data");
dataSet = canvas.GetDataSet(ppmWriter.GetPointFieldName());
ppmWriter.WriteToFile("ppmTestFile.ppm", dataSet);
{
vtkm::io::ImageReaderPNM reader("ppmTestFile.ppm");
dataSet = reader.ReadDataSet();
// TODO: Fix this
vtkm::cont::Field field = dataSet.GetField(reader.GetPointFieldName());
dataSet.AddPointField(ppmWriter.GetPointFieldName(), field.GetData());
vtkm::io::ImageWriterPNM writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
ppmWriter.WriteToFile("ppmTestFile2.ppm", dataSet);
{
vtkm::io::ImageReaderPNM reader("ppmTestFile2.ppm");
dataSet = reader.ReadDataSet();
TestFilledImage<PixelType>(dataSet, reader.GetPointFieldName(), canvas);
vtkm::io::ImageReaderPNM reader(filename);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
}
{
vtkm::io::ImageWriterPNM writer(filename);
writer.SetPixelDepth(pixelDepth);
writer.WriteDataSet(canvas.GetDataSet());
}
{
vtkm::io::ImageReaderPNM reader(filename);
vtkm::cont::DataSet dataSet = reader.ReadDataSet();
TestFilledImage(dataSet, reader.GetPointFieldName(), canvas);
}
}
void TestBaseImageMethods(const vtkm::rendering::Canvas& canvas)
{
TestCreateImageDataSet<RGBPixel_8>(canvas);
TestCreateImageDataSet<RGBPixel_16>(canvas);
TestCreateImageDataSet<GreyPixel_8>(canvas);
TestCreateImageDataSet<GreyPixel_16>(canvas);
TestCreateImageDataSet(canvas);
}
void TestPNMImage(const vtkm::rendering::Canvas& canvas)
{
TestReadAndWritePNM<8>(canvas);
TestReadAndWritePNM<16>(canvas);
TestReadAndWritePNM(canvas, "pnmRGB8Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_8);
TestReadAndWritePNM(canvas, "pnmRGB16Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_16);
}
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");
TestReadAndWritePNG(canvas, "pngRGB8Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_8);
TestReadAndWritePNG(canvas, "pngRGB16Test.png", vtkm::io::ImageWriterBase::PixelDepth::PIXEL_16);
}
void TestImage()