From 6c97054b7f2e4a53c3dce545a12c9c2bc0b9448f Mon Sep 17 00:00:00 2001 From: Kenneth Moreland Date: Thu, 28 May 2020 19:00:33 -0600 Subject: [PATCH] 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. --- vtkm/io/CMakeLists.txt | 8 +- vtkm/io/ImageWriter.h | 140 ------------------- vtkm/io/ImageWriter.hxx | 172 ------------------------ vtkm/io/ImageWriterBase.cxx | 88 ++++++++++++ vtkm/io/ImageWriterBase.h | 81 +++++++++++ vtkm/io/ImageWriterPNG.cxx | 69 ++++++++++ vtkm/io/ImageWriterPNG.h | 46 +++++++ vtkm/io/ImageWriterPNM.cxx | 65 +++++++++ vtkm/io/ImageWriterPNM.h | 53 ++++++++ vtkm/io/testing/UnitTestImageWriter.cxx | 120 ++++++++--------- 10 files changed, 464 insertions(+), 378 deletions(-) delete mode 100644 vtkm/io/ImageWriter.h delete mode 100644 vtkm/io/ImageWriter.hxx create mode 100644 vtkm/io/ImageWriterBase.cxx create mode 100644 vtkm/io/ImageWriterBase.h create mode 100644 vtkm/io/ImageWriterPNG.cxx create mode 100644 vtkm/io/ImageWriterPNG.h create mode 100644 vtkm/io/ImageWriterPNM.cxx create mode 100644 vtkm/io/ImageWriterPNM.h diff --git a/vtkm/io/CMakeLists.txt b/vtkm/io/CMakeLists.txt index 89a911173..399ba97b2 100644 --- a/vtkm/io/CMakeLists.txt +++ b/vtkm/io/CMakeLists.txt @@ -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 diff --git a/vtkm/io/ImageWriter.h b/vtkm/io/ImageWriter.h deleted file mode 100644 index 7ccc1284a..000000000 --- a/vtkm/io/ImageWriter.h +++ /dev/null @@ -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 -#include -#include - -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 - 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 - void WriteToFile(const std::string& fileName, const vtkm::cont::DataSet& dataSet) const; -}; - - -} // namespace io -} // namespace vtkm - -#include - -#endif diff --git a/vtkm/io/ImageWriter.hxx b/vtkm/io/ImageWriter.hxx deleted file mode 100644 index 6f1a46897..000000000 --- a/vtkm/io/ImageWriter.hxx +++ /dev/null @@ -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 -#include -#include -#include - -VTKM_THIRDPARTY_PRE_INCLUDE -#include -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(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(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(outStream, dataSet); - break; - case 255: - this->EncodeFile(outStream, dataSet); - break; - case 65535: - this->EncodeFile(outStream, dataSet); - break; - default: - throw vtkm::cont::ErrorBadValue("MaxColorValue: " + std::to_string(this->MaxColorValue) + - " was not one of: {255, 65535}"); - } -} - -VTKM_CONT -template -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>(); - auto pixelPortal = pixelField.ReadPortal(); - - vtkm::UInt32 imageSize = - static_cast(pixelField.GetNumberOfValues() * PixelType::BYTES_PER_PIXEL); - std::vector 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(fileName, dataSet); - break; - case 255: - WriteToFile(fileName, dataSet); - break; - case 65535: - WriteToFile(fileName, dataSet); - break; - default: - throw vtkm::cont::ErrorBadValue("MaxColorValue: " + std::to_string(this->MaxColorValue) + - " was not one of: {255, 65535}"); - } -} - -VTKM_CONT -template -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>(); - auto pixelPortal = pixelField.ReadPortal(); - std::vector imageData(static_cast::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(imageWidth), - static_cast(imageHeight), - PixelType::PNG_COLOR_TYPE, - PixelType::BIT_DEPTH); -} - -} // namespace io -} // namespace vtkm - -#endif diff --git a/vtkm/io/ImageWriterBase.cxx b/vtkm/io/ImageWriterBase.cxx new file mode 100644 index 000000000..fc1255ab3 --- /dev/null +++ b/vtkm/io/ImageWriterBase.cxx @@ -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 + +#include + +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()) + { + throw vtkm::cont::ErrorBadType( + "Image writers can only write data sets with 2D structured data."); + } + CellSetType cellSet = dataSet.GetCellSet().Cast(); + 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())) + { + 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()); +} +} +} // namespace vtkm::io diff --git a/vtkm/io/ImageWriterBase.h b/vtkm/io/ImageWriterBase.h new file mode 100644 index 000000000..11c14f538 --- /dev/null +++ b/vtkm/io/ImageWriterBase.h @@ -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 + +#include + +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_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 diff --git a/vtkm/io/ImageWriterPNG.cxx b/vtkm/io/ImageWriterPNG.cxx new file mode 100644 index 000000000..b5c2fdfcd --- /dev/null +++ b/vtkm/io/ImageWriterPNG.cxx @@ -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 + +#include + +VTKM_THIRDPARTY_PRE_INCLUDE +#include +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(width, height, pixels); + break; + case PixelDepth::PIXEL_16: + WriteToFile(width, height, pixels); + break; + } +} + +template +void ImageWriterPNG::WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels) +{ + auto pixelPortal = pixels.ReadPortal(); + std::vector imageData(static_cast::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(width), + static_cast(height), + PixelType::PNG_COLOR_TYPE, + PixelType::BIT_DEPTH); +} +} +} // namespace vtkm::io diff --git a/vtkm/io/ImageWriterPNG.h b/vtkm/io/ImageWriterPNG.h new file mode 100644 index 000000000..d2e8e53e6 --- /dev/null +++ b/vtkm/io/ImageWriterPNG.h @@ -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 + +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 + VTKM_CONT void WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels); +}; +} +} // namespace vtkm::io + +#endif //vtk_m_io_ImageWriterPNG_h diff --git a/vtkm/io/ImageWriterPNM.cxx b/vtkm/io/ImageWriterPNM.cxx new file mode 100644 index 000000000..a524a6697 --- /dev/null +++ b/vtkm/io/ImageWriterPNM.cxx @@ -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 + +#include + +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(width, height, pixels); + break; + case PixelDepth::PIXEL_16: + WriteToFile(width, height, pixels); + break; + } +} + +template +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(pixels.GetNumberOfValues() * PixelType::BYTES_PER_PIXEL); + std::vector 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 diff --git a/vtkm/io/ImageWriterPNM.h b/vtkm/io/ImageWriterPNM.h new file mode 100644 index 000000000..0ba088917 --- /dev/null +++ b/vtkm/io/ImageWriterPNM.h @@ -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 + +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 + VTKM_CONT void WriteToFile(vtkm::Id width, vtkm::Id height, const ColorArrayType& pixels); +}; +} +} // namespace vtkm::io + +#endif //vtk_m_io_ImageWriterPNM_h diff --git a/vtkm/io/testing/UnitTestImageWriter.cxx b/vtkm/io/testing/UnitTestImageWriter.cxx index 93f92a3f6..e92c3be5d 100644 --- a/vtkm/io/testing/UnitTestImageWriter.cxx +++ b/vtkm/io/testing/UnitTestImageWriter.cxx @@ -11,7 +11,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -24,7 +25,6 @@ namespace using namespace vtkm::io; using namespace vtkm::rendering; -template 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>().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 void TestCreateImageDataSet(const vtkm::rendering::Canvas& canvas) { + std::cout << "TestCreateImageDataSet" << std::endl; auto dataSet = canvas.GetDataSet("pixel-color"); - TestFilledImage(dataSet, "pixel-color", canvas); + TestFilledImage(dataSet, "pixel-color", canvas); } -template -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(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 -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; - 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(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(canvas); - TestCreateImageDataSet(canvas); - TestCreateImageDataSet(canvas); - TestCreateImageDataSet(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(canvas, "pngRGB8Test.png"); - TestReadAndWritePNG(canvas, "pngRGB16Test.png"); - TestReadAndWritePNG(canvas, "pngGrey8Test.png"); - TestReadAndWritePNG(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()