Add support for PNG plots using libpng

This commit is contained in:
Paul Romano 2021-09-30 16:28:05 -05:00
parent fa500e2896
commit 6eea13043f
9 changed files with 107 additions and 20 deletions

View file

@ -79,6 +79,11 @@ if(libmesh)
find_package(LIBMESH REQUIRED)
endif()
#===============================================================================
# libpng
#===============================================================================
find_package(PNG)
#===============================================================================
# HDF5 for binary output
#===============================================================================
@ -423,6 +428,11 @@ if(libmesh)
target_link_libraries(libopenmc PkgConfig::LIBMESH)
endif()
if (PNG_FOUND)
target_compile_definitions(libopenmc PRIVATE USE_LIBPNG)
target_link_libraries(libopenmc PNG::PNG)
endif()
#===============================================================================
# openmc executable
#===============================================================================

View file

@ -16,6 +16,8 @@ if(@libmesh@)
pkg_check_modules(LIBMESH REQUIRED @LIBMESH_PC_FILE@>=1.6.0 IMPORTED_TARGET)
endif()
find_package(PNG)
if(NOT TARGET OpenMC::libopenmc)
include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake")
endif()

View file

@ -239,11 +239,18 @@ public:
//! \param[out] image data associated with the plot object
void draw_mesh_lines(Plot const& pl, ImageData& data);
//! Write a ppm image to file using a plot object's image data
//! Write a PPM image using a plot object's image data
//! \param[in] plot object
//! \param[out] image data associated with the plot object
void output_ppm(Plot const& pl, const ImageData& data);
#ifdef USE_LIBPNG
//! Write a PNG image using a plot object's image data
//! \param[in] plot object
//! \param[out] image data associated with the plot object
void output_png(Plot const& pl, const ImageData& data);
#endif
//! Initialize a voxel file
//! \param[in] id of an open hdf5 file
//! \param[in] dimensions of the voxel file (dx, dy, dz)
@ -274,9 +281,9 @@ void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace);
//! Read plot specifications from a plots.xml file
void read_plots_xml();
//! Create a ppm image for a plot object
//! Create an image for a plot object
//! \param[in] plot object
void create_ppm(Plot const& pl);
void create_image(Plot const& pl);
//! Create an hdf5 voxel file for a plot object
//! \param[in] plot object

View file

@ -1,12 +1,16 @@
#include "openmc/plot.h"
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <sstream>
#include "xtensor/xview.hpp"
#include <fmt/core.h>
#include <fmt/ostream.h>
#ifdef USE_LIBPNG
#include <png.h>
#endif
#include "openmc/constants.h"
#include "openmc/error.h"
@ -103,17 +107,19 @@ uint64_t plotter_seed = 1;
extern "C" int openmc_plot_geometry()
{
for (auto& pl : model::plots) {
write_message(5, "Processing plot {}: {}...", pl.id_, pl.path_plot_);
if (PlotType::slice == pl.type_) {
// create 2D image
create_ppm(pl);
create_image(pl);
} else if (PlotType::voxel == pl.type_) {
// create voxel file for 3D viewing
create_voxel(pl);
}
}
return 0;
}
@ -139,11 +145,11 @@ void read_plots_xml()
}
//==============================================================================
// CREATE_PPM creates an image based on user input from a plots.xml <plot>
// specification in the portable pixmap format (PPM)
// CREATE_IMAGE creates an image based on user input from a plots.xml <plot>
// specification in the PNG/PPM format
//==============================================================================
void create_ppm(Plot const& pl)
void create_image(Plot const& pl)
{
size_t width = pl.pixels_[0];
@ -184,8 +190,12 @@ void create_ppm(Plot const& pl)
draw_mesh_lines(pl, data);
}
// write ppm data to file
// create image file
#ifdef USE_LIBPNG
output_png(pl, data);
#else
output_ppm(pl, data);
#endif
}
void Plot::set_id(pugi::xml_node plot_node)
@ -238,7 +248,11 @@ void Plot::set_output_path(pugi::xml_node plot_node)
// add appropriate file extension to name
switch (type_) {
case PlotType::slice:
#ifdef USE_LIBPNG
filename.append(".png");
#else
filename.append(".ppm");
#endif
break;
case PlotType::voxel:
filename.append(".h5");
@ -673,6 +687,60 @@ void output_ppm(Plot const& pl, const ImageData& data)
of << "\n";
}
//==============================================================================
// OUTPUT_PNG writes out a previously generated image to a PNG file
//==============================================================================
#ifdef USE_LIBPNG
void output_png(Plot const& pl, const ImageData& data)
{
// Open PNG file for writing
std::string fname = pl.path_plot_;
fname = strtrim(fname);
auto fp = std::fopen(fname.c_str(), "wb");
// Initialize write and info structures
auto png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
auto info_ptr = png_create_info_struct(png_ptr);
// Setup exception handling
if (setjmp(png_jmpbuf(png_ptr)))
fatal_error("Error during png creation");
png_init_io(png_ptr, fp);
// Write header (8 bit colour depth)
int width = pl.pixels_[0];
int height = pl.pixels_[1];
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
// Allocate memory for one row (3 bytes per pixel - RGB)
std::vector<png_byte> row(3 * width);
// Write color for each pixel
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
RGBColor rgb = data(x, y);
row[3 * x] = rgb.red;
row[3 * x + 1] = rgb.green;
row[3 * x + 2] = rgb.blue;
}
png_write_row(png_ptr, row.data());
}
// End write
png_write_end(png_ptr, nullptr);
// Clean up data structures
std::fclose(fp);
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
png_destroy_write_struct(&png_ptr, nullptr);
}
#endif
//==============================================================================
// DRAW_MESH_LINES draws mesh line boundaries on an image
//==============================================================================
@ -777,7 +845,7 @@ void draw_mesh_lines(Plot const& pl, ImageData& data)
//==============================================================================
// CREATE_VOXEL outputs a binary file that can be input into silomesh for 3D
// geometry visualization. It works the same way as create_ppm by dragging a
// geometry visualization. It works the same way as create_image by dragging a
// particle across the geometry for the specified number of voxels. The first 3
// int's in the binary are the number of x, y, and z voxels. The next 3
// double's are the widths of the voxels in the x, y, and z directions. The

View file

@ -1 +1 @@
a8192f6029cf99748816fab2618fa48e180656eba313940ccdfff3e56890a5dadd13bf92cd7e3be6a4b535bca5e2a58a905fcfba018fb922273f8be6dfc19859
f85c20735a0c08525fe48b19a8e075c074539ee6c8860268fa0cb515d842496b7086e5b94305ef78dcf2106bb193abdf438259ac8ff1d0245a2782eb6f5af873

View file

@ -19,7 +19,7 @@ class PlotTestHarness(TestHarness):
openmc.plot_geometry(openmc_exec=config['exe'])
def _test_output_created(self):
"""Make sure *.ppm has been created."""
"""Make sure *.png has been created."""
for fname in self._plot_names:
assert os.path.exists(fname), 'Plot output file does not exist.'
@ -34,8 +34,8 @@ class PlotTestHarness(TestHarness):
outstr = bytes()
for fname in self._plot_names:
if fname.endswith('.ppm'):
# Add PPM output to results
if fname.endswith('.png'):
# Add PNG output to results
with open(fname, 'rb') as fh:
outstr += fh.read()
elif fname.endswith('.h5'):
@ -56,6 +56,6 @@ class PlotTestHarness(TestHarness):
def test_plot():
harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm',
harness = PlotTestHarness(('plot_1.png', 'plot_2.png', 'plot_3.png',
'plot_4.h5'))
harness.main()

View file

@ -1 +1 @@
566103831cb8273b0578565c39d30e479664e2b1783d877b45b42cf4f3af0b01671b6db423114b09a74bbe1ddf51a7db565ff2118d6d1ee987b52318773b719a
926065ceb2a9b8292fe6270317c38c4373473cfea19d2a8392a32e5ece8e314c04b9f032921d987bd195ae4b6f674d359b0e38302e6ae4c93b4ac9573a384ac6

View file

@ -19,7 +19,7 @@ class PlotTestHarness(TestHarness):
openmc.plot_geometry(openmc_exec=config['exe'])
def _test_output_created(self):
"""Make sure *.ppm has been created."""
"""Make sure *.png has been created."""
for fname in self._plot_names:
assert os.path.exists(fname), 'Plot output file does not exist.'
@ -34,8 +34,8 @@ class PlotTestHarness(TestHarness):
outstr = bytes()
for fname in self._plot_names:
if fname.endswith('.ppm'):
# Add PPM output to results
if fname.endswith('.png'):
# Add PNG output to results
with open(fname, 'rb') as fh:
outstr += fh.read()
elif fname.endswith('.h5'):
@ -56,6 +56,6 @@ class PlotTestHarness(TestHarness):
def test_plot_overlap():
harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm',
harness = PlotTestHarness(('plot_1.png', 'plot_2.png', 'plot_3.png',
'plot_4.h5'))
harness.main()

View file

@ -25,7 +25,7 @@ class PlotVoxelTestHarness(TestHarness):
glob.glob('plot_4.h5'))
def _test_output_created(self):
"""Make sure *.ppm has been created."""
"""Make sure plots have been created."""
for fname in self._plot_names:
assert os.path.exists(fname), 'Plot output file does not exist.'