https://mooseframework.inl.gov
PNGOutput.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #ifdef MOOSE_HAVE_LIBPNG
11 
12 #include <fstream>
13 #include "PNGOutput.h"
14 #include "FEProblemBase.h"
15 #include "NonlinearSystem.h"
16 #include "AuxiliarySystem.h"
17 #include "libmesh/mesh_tools.h"
18 
19 registerMooseObject("MooseApp", PNGOutput);
20 
23 {
25  params.addParam<bool>("transparent_background",
26  false,
27  "Determination of whether the background will be transparent.");
28  params.addRequiredParam<VariableName>("variable",
29  "The name of the variable to use when creating the image");
30  params.addParam<Real>("max",
31  "The maximum for the variable we want to use. If unspecified, the maximum "
32  "of the variable's DOF values is used");
33  params.addParam<Real>("min",
34  "The minimum for the variable we want to use. If unspecified, the minimum "
35  "of the variable's DOF values is used");
36 
37  params.addParam<Point>("frame_center",
38  Point(0, 0, 0),
39  "Center of the frame when defining the rectangular plotting region "
40  "dimensions. Applied after the frame rotation");
41  params.addParam<Point>(
42  "first_axis",
43  Point(1, 0, 0),
44  "First axis to generate the rectangular plotting region. The axis vector will be normalized");
45  params.addParam<Point>("second_axis",
46  Point(0, 1, 0),
47  "Second axis to generate the rectangular plotting region. The axis vector "
48  "will be normalized");
49  params.addParam<Real>(
50  "min_x",
51  "Minimum coordinate along the first axis. Defaults to mesh bounding box min X value");
52  params.addParam<Real>(
53  "max_x",
54  "Maximum coordinate along the first axis. Defaults to mesh bounding box max X value");
55  params.addParam<Real>(
56  "min_y",
57  "Minimum coordinate along the second axis. Default to mesh bounding box min Y value");
58  params.addParam<Real>(
59  "max_y",
60  "Maximum coordinate along the second axis. Default to mesh bounding box max Y value");
61  params.addParamNamesToGroup("frame_center first_axis second_axis min_x max_x min_y max_y",
62  "Rectangular box extent");
63 
64  MooseEnum color("GRAY BRYW BWR RWB BR", "BR");
65  params.addRequiredParam<MooseEnum>("color", color, "Choose the color scheme to use.");
66  params.addRangeCheckedParam<unsigned int>(
67  "resolution", 25, "resolution>0", "The length of the longest side of the image in pixels.");
68  params.addRangeCheckedParam<Real>("out_bounds_shade",
69  .5,
70  "out_bounds_shade>=0 & out_bounds_shade<=1",
71  "Color for the parts of the image that are out of bounds."
72  "Value is between 1 and 0.");
73  params.addRangeCheckedParam<Real>("transparency",
74  1,
75  "transparency>=0 & transparency<=1",
76  "Value is between 0 and 1"
77  "where 0 is completely transparent and 1 is completely opaque. "
78  "Default transparency of the image is no transparency.");
79  params.addParamNamesToGroup("color resolution out_bounds_shade transparency", "Plotting");
80  params.addClassDescription("Output data in the PNG format");
81  return params;
82 }
83 
85  : FileOutput(parameters),
86  _resolution(getParam<unsigned int>("resolution")),
87  _color(parameters.get<MooseEnum>("color")),
88  _transparent_background(getParam<bool>("transparent_background")),
89  _transparency(getParam<Real>("transparency")),
90  _nl_sys_num(libMesh::invalid_uint),
91  _variable(getParam<VariableName>("variable")),
92  _max(isParamValid("max") ? getParam<Real>("max") : std::numeric_limits<Real>::max()),
93  _min(isParamValid("min") ? getParam<Real>("min") : -std::numeric_limits<Real>::max()),
94  _out_bounds_shade(getParam<Real>("out_bounds_shade"))
95 {
96  if (!MooseUtils::absoluteFuzzyEqual(
97  getParam<Point>("first_axis") * getParam<Point>("second_axis"), 0))
98  paramWarning("first_axis", "The two axis are not orthogonal");
99 }
100 
101 // Function for making the _mesh_function object.
102 void
104 {
105  // The number assigned to the variable. Used to build the correct mesh. Default is 0.
106  unsigned int variable_number = 0;
107 
108  // PNGOutput does not currently scale for running in parallel.
109  if (processor_id() != 0)
110  mooseInfo("PNGOutput is not currently scalable.");
111 
112  bool var_found = false;
114  {
115  variable_number = _problem_ptr->getAuxiliarySystem().getVariable(0, _variable).number();
116  var_found = true;
117  }
118 
119  else
120  for (const auto nl_sys_num : make_range(_problem_ptr->numNonlinearSystems()))
122  {
123  variable_number =
125  _nl_sys_num = nl_sys_num;
126  var_found = true;
127  }
128 
129  if (!var_found)
130  paramError("variable", "This doesn't exist.");
131 
132  const std::vector<unsigned int> var_nums = {variable_number};
133 
134  // If we want the background to be transparent, we need a number over 1.
136  _out_bounds_shade = 2;
137 
138  // Find the values that will be used for rescaling purposes.
140 
141  // Set up the mesh_function
143  _mesh_function = std::make_unique<libMesh::MeshFunction>(
144  *_es_ptr,
147  var_nums);
148  else
149  _mesh_function = std::make_unique<libMesh::MeshFunction>(
150  *_es_ptr,
153  var_nums);
154  _mesh_function->init();
155 
156  // Need to enable out of mesh with the given control color scaled in reverse
157  // so when scaling is done, this value retains it's original value.
158  _mesh_function->enable_out_of_mesh_mode(reverseScale(_out_bounds_shade));
159 }
160 
161 // Function to find the min and max values so that all the values can be scaled between the two.
162 void
164 {
165  // The max and min.
166  // If the max value wasn't specified in the input file, find it from the system.
167  if (!_pars.isParamValid("max"))
168  {
171  else
173  }
174  else
175  _scaling_max = _max;
176 
177  // If the min value wasn't specified in the input file, find it from the system.
178  if (!_pars.isParamValid("min"))
179  {
182  else
184  }
185  else
186  _scaling_min = _min;
187 
188  // The amount the values will need to be shifted.
189  _shift_value = 0;
190 
191  // Get the shift value.
192  if (_scaling_min != 0)
193  {
194  // Shiftvalue will be the same magnitude, but
195  // going in the opposite direction of the scalingMin
197  }
198 
199  // Shift the max.
201 }
202 
203 // Function to apply the scale to the data points.
204 // Needed to be able to see accurate images that cover the appropriate color spectrum.
205 inline Real
206 PNGOutput::applyScale(Real value_to_scale)
207 {
208  return ((value_to_scale + _shift_value) / _scaling_max);
209 }
210 
211 // Function to reverse the scaling that happens to a value.
212 // Needed to be able to accurately control the _out_bounds_shade.
213 inline Real
214 PNGOutput::reverseScale(Real value_to_unscale)
215 {
216  return ((value_to_unscale * _scaling_max) - _shift_value);
217 }
218 
219 // Function that controls the colorization of the png image for non-grayscale images.
220 void
221 PNGOutput::setRGB(png_byte * rgb, Real selection)
222 {
223  // With this system we have a color we start with when the value is 0 and another it approaches as
224  // the value increases all the way to 255. If we want it to approach another color from that new
225  // color, it will do so for the next 255, so the transition is from 256 - 511. For each
226  // additional color we want to transition to, we need another 255. Transitioning from no color, or
227  // black to Red then Green then Blue then the values of from black as it becomes Red would be 0 -
228  // 255, Red to Green as 256 - 511 and then Green to Blue as 512 - 767 which gives us our total
229  // colorSpectrum of 0 - 767, which includes those colors and each of their states in the
230  // transistion.
231  unsigned int number_of_destination_colors = 1;
232  switch (_color)
233  {
234  // BRYW. Three destination colors (R,Y,W).
235  case 1:
236  number_of_destination_colors = 3;
237  break;
238 
239  // BWR. Two destination colors (W,R).
240  case 2:
241  number_of_destination_colors = 2;
242  break;
243 
244  // RWB. Two destination colors (W,B).
245  case 3:
246  number_of_destination_colors = 2;
247  break;
248 
249  // BR. One destination color (R).
250  case 4:
251  number_of_destination_colors = 1;
252  break;
253  }
254 
255  // We need to convert the number of colors into the spectrum max, then convert the value from the
256  // mesh to a point somewhere in the range of 0 to color_spectrum_max.
257  auto color_spectrum_max = (256 * number_of_destination_colors) - 1;
258  auto color = (unsigned int)(selection * color_spectrum_max);
259 
260  // Unless we specifically say some part is transparent, we want the whole image to be opaque.
261  auto tran = (unsigned int)(_transparency * 255);
262 
263  // Make sure everything is within our colorSpectrum. If it's bigger, then we want a
264  // transparent background.
265  if (color > color_spectrum_max)
266  {
267  color = color_spectrum_max;
268  tran = 0;
269  }
270 
271  auto magnitude = color % 256;
272 
273  switch (_color)
274  {
275  // Current color scheme: Blue->Red->Yellow->White
276  case 1:
277  // Blue->Red
278  if (color < 256)
279  {
280  rgb[0] = magnitude;
281  rgb[1] = 0;
282  rgb[2] = 50; // 255 - magnitude;
283  }
284  // Red->Yellow
285  else if (color < 512)
286  {
287  rgb[0] = 255;
288  rgb[1] = magnitude;
289  rgb[2] = 0;
290  }
291  // Yellow->White
292  else
293  {
294  rgb[0] = 255;
295  rgb[1] = 255;
296  rgb[2] = magnitude;
297  }
298  break;
299 
300  // Color Scheme: Blue->White->Red
301  // Using the RGB values found in Paraview
302  case 2:
303  // Blue->White
304  if (color < 256)
305  {
306  rgb[0] = (int)(255.0 * (0.231373 + (0.002485 * (float)magnitude)));
307  rgb[1] = (int)(255.0 * (0.298039 + (0.002223 * (float)magnitude)));
308  rgb[2] = (int)(255.0 * (0.752941 + (0.000439 * (float)magnitude)));
309  }
310  // White->Red
311  else
312  {
313  rgb[0] = (int)(255.0 * (0.865003 - (0.000624 * (float)magnitude)));
314  rgb[1] = (int)(255.0 * (0.865003 - (0.003331 * (float)magnitude)));
315  rgb[2] = (int)(255.0 * (0.865003 - (0.002808 * (float)magnitude)));
316  }
317  break;
318 
319  // Red->White->Blue
320  case 3:
321  // Red->White
322  if (color < 256)
323  {
324  rgb[0] = 255;
325  rgb[1] = magnitude;
326  rgb[2] = magnitude;
327  }
328  // White->Blue
329  else
330  {
331  rgb[0] = 255 - magnitude;
332  rgb[1] = 255 - magnitude;
333  rgb[2] = 255;
334  }
335  break;
336 
337  // Blue->Red
338  case 4:
339  // Blue->Red
340  rgb[0] = magnitude;
341  rgb[1] = 0;
342  rgb[2] = 255 - magnitude;
343  break;
344  }
345  // Add any transparency.
346  rgb[3] = tran;
347 }
348 
349 void
351 {
352  makeMeshFunc();
353  _box = MeshTools::create_bounding_box(*_mesh_ptr);
354 
355  // Make sure this happens on processor 0
356  if (processor_id() == 0)
357  makePNG();
358 }
359 
360 // Function the writes the PNG out to the appropriate filename.
361 void
363 {
364  // Get the max and min of the BoundingBox
365  Point max_point = _box.max();
366  Point min_point = _box.min();
367 
368  // Modify the bounding box if the user passed different bounds
369  if (isParamValid("min_x"))
370  min_point(0) = getParam<Real>("min_x");
371  if (isParamValid("min_y"))
372  min_point(1) = getParam<Real>("min_y");
373  if (isParamValid("max_x"))
374  max_point(0) = getParam<Real>("max_x");
375  if (isParamValid("max_y"))
376  max_point(1) = getParam<Real>("max_y");
377 
378  // The total distance on the two plotting axes.
379  Real dist_x = max_point(0) - min_point(0);
380  Real dist_y = max_point(1) - min_point(1);
381 
382  // Width and height for the PNG image.
383  Real width;
384  Real height;
385 
386  // The longer dimension becomes the value to which we scale the other.
387  if (dist_x > dist_y)
388  {
389  width = _resolution;
390  height = (_resolution / dist_x) * dist_y;
391  }
392  else
393  {
394  height = _resolution;
395  width = (_resolution / dist_y) * dist_x;
396  }
397 
398  // Create the filename based on base and the test step number.
399  std::ostringstream png_file;
400  png_file << _file_base << "_" << std::setfill('0') << std::setw(3) << _t_step << ".png";
401 
402  // libpng is built on C, so by default it takes FILE*.
403  FILE * fp = nullptr;
404  png_structp pngp = nullptr;
405  png_infop infop = nullptr;
406  // Required depth for proper image clarity.
407  Real depth = 8;
408  // Allocate resources.
409  std::vector<png_byte> row((width + 1) * 4);
410 
411  // Check if we can open and write to the file.
412  MooseUtils::checkFileWriteable(png_file.str());
413 
414  // Open the file with write and bit modes.
415  fp = fopen(png_file.str().c_str(), "wb");
416 
417  // Verify the libpng we are linked against at runtime matches the headers we
418  // were compiled against. PNG_LIBPNG_VER is major*10000 + minor*100 + release;
419  // libpng only guarantees ABI within the same major.minor, so a mismatch there
420  // can cause png_create_write_struct to segfault rather than fail cleanly.
421  const png_uint_32 runtime_libpng_ver = png_access_version_number();
422  if (runtime_libpng_ver / 100 != PNG_LIBPNG_VER / 100)
423  {
424  mooseWarning("PNG output skipped for ",
425  png_file.str(),
426  ": runtime libpng version ",
427  runtime_libpng_ver,
428  " is ABI-incompatible with the version ",
429  static_cast<png_uint_32>(PNG_LIBPNG_VER),
430  " used at compile time.");
431  if (fp != nullptr)
432  fclose(fp);
433  return;
434  }
435 
436  pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
437  if (!pngp)
438  mooseError("Failed to make the pointer string for the png.");
439 
440  infop = png_create_info_struct(pngp);
441  if (!infop)
442  mooseError("Failed to make an info pointer for the png.");
443 
444  // Initializes the IO for the png. Needs FILE* to compile.
445  png_init_io(pngp, fp);
446 
447  // Set up the PNG header.
448  png_set_IHDR(pngp,
449  infop,
450  width,
451  height,
452  depth,
453  (_color ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_GRAY),
454  PNG_INTERLACE_NONE,
455  PNG_COMPRESSION_TYPE_DEFAULT,
456  PNG_FILTER_TYPE_DEFAULT);
457 
458  png_write_info(pngp, infop);
459 
460  // Initializing the point that will be used for populating the mesh values.
461  Point pt(0, 0, 0);
462 
463  // Pre-compute the transformations
464  Point center(0, 0, 0);
465  if (isParamValid("frame_center"))
466  center = getParam<Point>("frame_center");
467  else
468  center = Point((min_point(0) + max_point(0)) / 2, (min_point(1) + max_point(1)) / 2, 0);
469 
470  const auto first_axis = getParam<Point>("first_axis").unit();
471  const auto second_axis = getParam<Point>("second_axis").unit();
472 
473  // Dense vector that we can pass into the _mesh_function to fill with a value for a given point.
474  DenseVector<Number> dv(0);
475 
476  // Loop through to create the image.
477  for (const auto iy : make_range((unsigned int)height))
478  {
479  for (const auto ix : make_range((unsigned int)width + 1))
480  {
481  // Move along the axis from the center
482  // Center the point in the pixel
483  pt = center +
484  (min_point(0) + Real(ix) / _resolution * (max_point(0) - min_point(0))) * first_axis +
485  (min_point(1) + Real(iy) / _resolution * (max_point(1) - min_point(1))) * second_axis;
486 
487  (*_mesh_function)(pt, _time, dv, nullptr);
488 
489  // Determine whether to create the PNG in color or grayscale
490  if (_color)
491  setRGB(&row.data()[ix * 4], applyScale(dv(0)));
492  else
493  row.data()[ix] = applyScale(dv(0)) * 255;
494  }
495  png_write_row(pngp, row.data());
496  }
497 
498  // Close the file and take care of some other png end stuff.
499  png_write_end(pngp, nullptr);
500  if (fp != nullptr)
501  fclose(fp);
502  if (infop != nullptr)
503  png_free_data(pngp, infop, PNG_FREE_ALL, -1);
504  if (pngp != nullptr)
505  png_destroy_write_struct(&pngp, &infop);
506 }
507 
508 #endif
unsigned int _nl_sys_num
What nonlinear system the variable is in.
Definition: PNGOutput.h:74
void mooseInfo(Args &&... args) const
Definition: MooseBase.h:344
Real _min
Definition: PNGOutput.h:82
void makePNG()
Function that creates the PNG.
Definition: PNGOutput.C:362
const InputParameters & _pars
The object&#39;s parameters.
Definition: MooseBase.h:394
std::unique_ptr< libMesh::MeshFunction > _mesh_function
Pointer to the libMesh::MeshFunction object in which the read data is stored.
Definition: PNGOutput.h:67
const unsigned int invalid_uint
const MooseEnum _color
Way to specify color vs grayscale image creation.
Definition: PNGOutput.h:58
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
virtual std::size_t numNonlinearSystems() const override
void makeMeshFunc()
Function to create the mesh_function.
Definition: PNGOutput.C:103
static InputParameters validParams()
Definition: PNGOutput.C:22
unsigned int number() const
Get variable number coming from libMesh.
const bool _transparent_background
Indicates whether to make the background transparent.
Definition: PNGOutput.h:61
Real reverseScale(Real value_to_unscale)
Function for reversing the applyScale function.
Definition: PNGOutput.C:214
Real _out_bounds_shade
Value of the colors that are outside of the libmesh bounds.
Definition: PNGOutput.h:90
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::string _file_base
The base filename from the input paramaters.
Definition: FileOutput.h:89
void calculateRescalingValues()
Function to populate values to the variables used for scaling.
Definition: PNGOutput.C:163
int & _t_step
The current time step.
Definition: Output.h:220
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
Output class to create a 2D figure in PNG format.
Definition: PNGOutput.h:25
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
auto max(const L &left, const R &right)
const unsigned int _resolution
Variable to determine the size, or resolution, of the image.
Definition: PNGOutput.h:55
void mooseWarning(Args &&... args) const
virtual libMesh::DofMap & dofMap()
Gets writeable reference to the dof map.
Definition: SystemBase.C:1164
virtual NumericVector< Number > & serializedSolution()
Returns a reference to a serialized version of the solution vector for this subproblem.
Definition: SystemBase.C:1635
registerMooseObject("MooseApp", PNGOutput)
FEProblemBase * _problem_ptr
Pointer the the FEProblemBase object for output object (use this)
Definition: Output.h:185
const Real _transparency
Controls transparency level for the general image.
Definition: PNGOutput.h:64
static InputParameters validParams()
Definition: FileOutput.C:24
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
Real _scaling_max
Definition: PNGOutput.h:86
Real _max
Variables that store the max and min of the values in the variable used.
Definition: PNGOutput.h:81
Real _shift_value
Definition: PNGOutput.h:87
virtual Real max() const =0
virtual Real min() const =0
AuxiliarySystem & getAuxiliarySystem()
virtual bool hasVariable(const std::string &var_name) const
Query a system for a variable.
Definition: SystemBase.C:852
virtual NonlinearSystem & getNonlinearSystem(const unsigned int sys_num)
Real applyScale(Real value_to_scale)
Function for applying scaling to given values.
Definition: PNGOutput.C:206
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Real _scaling_min
Values used for rescaling the image.
Definition: PNGOutput.h:85
BoundingBox _box
The boundaries of the image.
Definition: PNGOutput.h:70
libMesh::EquationSystems * _es_ptr
Reference the the libMesh::EquationSystems object that contains the data.
Definition: Output.h:194
VariableName _variable
The name of the variable to use to create the png.
Definition: PNGOutput.h:77
IntRange< T > make_range(T beg, T end)
MooseMesh * _mesh_ptr
A convenience pointer to the current mesh (reference or displaced depending on "use_displaced") ...
Definition: Output.h:197
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:281
PNGOutput(const InputParameters &parameters)
Definition: PNGOutput.C:84
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
bool checkFileWriteable(const std::string &filename, bool throw_on_unwritable)
Definition: MooseUtils.C:307
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:209
MooseVariableFieldBase & getVariable(THREAD_ID tid, const std::string &var_name) const
Gets a reference to a variable of with specified name.
Definition: SystemBase.C:91
void paramWarning(const std::string &param, Args... args) const
An outputter with filename support.
Definition: FileOutput.h:20
void setRGB(png_byte *rgb, Real selection)
Method for assigning color values to the PNG.
Definition: PNGOutput.C:221
processor_id_type processor_id() const
Real & _time
The current time for output purposes.
Definition: Output.h:214
void ErrorVector unsigned int
const Elem & get(const ElemType type_in)
virtual void output()
Called to run the functions in this class.
Definition: PNGOutput.C:350
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...
bool isParamValid(const std::string &name) const
This method returns parameters that have been initialized in one fashion or another, i.e.