LCOV - code coverage report
Current view: top level - src/outputs/png - PNGOutput.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 419b9d Lines: 155 200 77.5 %
Date: 2025-08-08 20:01:16 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      21             : InputParameters
      22       14337 : PNGOutput::validParams()
      23             : {
      24       14337 :   InputParameters params = FileOutput::validParams();
      25       43011 :   params.addParam<bool>("transparent_background",
      26       28674 :                         false,
      27             :                         "Determination of whether the background will be transparent.");
      28       14337 :   params.addRequiredParam<VariableName>("variable",
      29             :                                         "The name of the variable to use when creating the image");
      30       14337 :   params.addParam<Real>("max", 1, "The maximum for the variable we want to use");
      31       14337 :   params.addParam<Real>("min", 0, "The minimum for the variable we want to use");
      32       14337 :   MooseEnum color("GRAY BRYW BWR RWB BR");
      33       14337 :   params.addRequiredParam<MooseEnum>("color", color, "Choose the color scheme to use.");
      34       43011 :   params.addRangeCheckedParam<unsigned int>(
      35       28674 :       "resolution", 25, "resolution>0", "The length of the longest side of the image in pixels.");
      36       43011 :   params.addRangeCheckedParam<Real>("out_bounds_shade",
      37       28674 :                                     .5,
      38             :                                     "out_bounds_shade>=0 & out_bounds_shade<=1",
      39             :                                     "Color for the parts of the image that are out of bounds."
      40             :                                     "Value is between 1 and 0.");
      41       14337 :   params.addRangeCheckedParam<Real>("transparency",
      42             :                                     1,
      43             :                                     "transparency>=0 & transparency<=1",
      44             :                                     "Value is between 0 and 1"
      45             :                                     "where 0 is completely transparent and 1 is completely opaque. "
      46             :                                     "Default transparency of the image is no transparency.");
      47       14337 :   params.addClassDescription("Output data in the PNG format");
      48       28674 :   return params;
      49       14337 : }
      50             : 
      51          36 : PNGOutput::PNGOutput(const InputParameters & parameters)
      52             :   : FileOutput(parameters),
      53          36 :     _resolution(getParam<unsigned int>("resolution")),
      54          36 :     _color(parameters.get<MooseEnum>("color")),
      55          36 :     _transparent_background(getParam<bool>("transparent_background")),
      56          36 :     _transparency(getParam<Real>("transparency")),
      57          36 :     _nl_sys_num(libMesh::invalid_uint),
      58          36 :     _variable(getParam<VariableName>("variable")),
      59          36 :     _max(getParam<Real>("max")),
      60          36 :     _min(getParam<Real>("min")),
      61         108 :     _out_bounds_shade(getParam<Real>("out_bounds_shade"))
      62             : {
      63          36 : }
      64             : 
      65             : // Funtion for making the _mesh_function object.
      66             : void
      67         354 : PNGOutput::makeMeshFunc()
      68             : {
      69             : 
      70             :   // The number assigned to the variable.  Used to build the correct mesh.  Default is 0.
      71         354 :   unsigned int variable_number = 0;
      72             : 
      73             :   // PNGOutput does not currently scale for running in parallel.
      74         354 :   if (processor_id() != 0)
      75          84 :     mooseInfo("PNGOutput is not currently scalable.");
      76             : 
      77         354 :   bool var_found = false;
      78         354 :   if (_problem_ptr->getAuxiliarySystem().hasVariable(_variable))
      79             :   {
      80          84 :     variable_number = _problem_ptr->getAuxiliarySystem().getVariable(0, _variable).number();
      81          84 :     var_found = true;
      82             :   }
      83             : 
      84             :   else
      85         540 :     for (const auto nl_sys_num : make_range(_problem_ptr->numNonlinearSystems()))
      86         270 :       if (_problem_ptr->getNonlinearSystem(nl_sys_num).hasVariable(_variable))
      87             :       {
      88             :         variable_number =
      89         270 :             _problem_ptr->getNonlinearSystem(nl_sys_num).getVariable(0, _variable).number();
      90         270 :         _nl_sys_num = nl_sys_num;
      91         270 :         var_found = true;
      92             :       }
      93             : 
      94         354 :   if (!var_found)
      95           0 :     paramError("variable", "This doesn't exist.");
      96             : 
      97         354 :   const std::vector<unsigned int> var_nums = {variable_number};
      98             : 
      99             :   // If we want the background to be transparent, we need a number over 1.
     100         354 :   if (_transparent_background)
     101           0 :     _out_bounds_shade = 2;
     102             : 
     103             :   // Find the values that will be used for rescaling purposes.
     104         354 :   calculateRescalingValues();
     105             : 
     106             :   // Set up the mesh_function
     107         354 :   if (_nl_sys_num == libMesh::invalid_uint)
     108          84 :     _mesh_function = std::make_unique<libMesh::MeshFunction>(
     109          84 :         *_es_ptr,
     110          84 :         _problem_ptr->getAuxiliarySystem().serializedSolution(),
     111          84 :         _problem_ptr->getAuxiliarySystem().dofMap(),
     112          84 :         var_nums);
     113             :   else
     114         270 :     _mesh_function = std::make_unique<libMesh::MeshFunction>(
     115         270 :         *_es_ptr,
     116         270 :         _problem_ptr->getNonlinearSystem(_nl_sys_num).serializedSolution(),
     117         270 :         _problem_ptr->getNonlinearSystem(_nl_sys_num).dofMap(),
     118         270 :         var_nums);
     119         354 :   _mesh_function->init();
     120             : 
     121             :   // Need to enable out of mesh with the given control color scaled in reverse
     122             :   // so when scaling is done, this value retains it's original value.
     123         354 :   _mesh_function->enable_out_of_mesh_mode(reverseScale(_out_bounds_shade));
     124         354 : }
     125             : 
     126             : // Function to find the min and max values so that all the values can be scaled between the two.
     127             : void
     128         354 : PNGOutput::calculateRescalingValues()
     129             : {
     130             :   // The max and min.
     131             :   // If the max value wasn't specified in the input file, find it from the system.
     132         354 :   if (!_pars.isParamSetByUser("max"))
     133             :   {
     134         354 :     if (_nl_sys_num == libMesh::invalid_uint)
     135          84 :       _scaling_max = _problem_ptr->getAuxiliarySystem().serializedSolution().max();
     136             :     else
     137         270 :       _scaling_max = _problem_ptr->getNonlinearSystem(_nl_sys_num).serializedSolution().max();
     138             :   }
     139             :   else
     140           0 :     _scaling_max = _max;
     141             : 
     142             :   // If the min value wasn't specified in the input file, find it from the system.
     143         354 :   if (!_pars.isParamSetByUser("min"))
     144             :   {
     145         354 :     if (_nl_sys_num == libMesh::invalid_uint)
     146          84 :       _scaling_min = _problem_ptr->getAuxiliarySystem().serializedSolution().min();
     147             :     else
     148         270 :       _scaling_min = _problem_ptr->getNonlinearSystem(_nl_sys_num).serializedSolution().min();
     149             :   }
     150             :   else
     151           0 :     _scaling_min = _min;
     152             : 
     153             :   // The amount the values will need to be shifted.
     154         354 :   _shift_value = 0;
     155             : 
     156             :   // Get the shift value.
     157         354 :   if (_scaling_min != 0)
     158             :   {
     159             :     // Shiftvalue will be the same magnitude, but
     160             :     // going in the opposite direction of the scalingMin
     161          80 :     _shift_value -= _scaling_min;
     162             :   }
     163             : 
     164             :   // Shift the max.
     165         354 :   _scaling_max += _shift_value;
     166         354 : }
     167             : 
     168             : // Function to apply the scale to the data points.
     169             : // Needed to be able to see accurate images that cover the appropriate color spectrum.
     170             : inline Real
     171      150300 : PNGOutput::applyScale(Real value_to_scale)
     172             : {
     173      150300 :   return ((value_to_scale + _shift_value) / _scaling_max);
     174             : }
     175             : 
     176             : // Function to reverse the scaling that happens to a value.
     177             : // Needed to be able to accurately control the _out_bounds_shade.
     178             : inline Real
     179         354 : PNGOutput::reverseScale(Real value_to_unscale)
     180             : {
     181         354 :   return ((value_to_unscale * _scaling_max) - _shift_value);
     182             : }
     183             : 
     184             : // Function that controls the colorization of the png image for non-grayscale images.
     185             : void
     186      150300 : PNGOutput::setRGB(png_byte * rgb, Real selection)
     187             : {
     188             :   // With this system we have a color we start with when the value is 0 and another it approaches as
     189             :   // the value increases all the way to 255.  If we want it to approach another color from that new
     190             :   // color, it will do so for the next 255, so the transition is from 256 - 511.  For each
     191             :   // additional color we want to transition to, we need another 255. Transitioning from no color, or
     192             :   // black to Red then Green then Blue then the values of from black as it becomes Red would be 0 -
     193             :   // 255, Red to Green as 256 - 511 and then Green to Blue as 512 - 767 which gives us our total
     194             :   // colorSpectrum of 0 - 767, which includes those colors and each of their states in the
     195             :   // transistion.
     196      150300 :   unsigned int number_of_destination_colors = 1;
     197      150300 :   switch (_color)
     198             :   {
     199             :     // BRYW.  Three destination colors (R,Y,W).
     200           0 :     case 1:
     201           0 :       number_of_destination_colors = 3;
     202           0 :       break;
     203             : 
     204             :     // BWR.  Two destination colors (W,R).
     205           0 :     case 2:
     206           0 :       number_of_destination_colors = 2;
     207           0 :       break;
     208             : 
     209             :     // RWB.  Two destination colors (W,B).
     210      150300 :     case 3:
     211      150300 :       number_of_destination_colors = 2;
     212      150300 :       break;
     213             : 
     214             :     // BR.   One destination color (R).
     215           0 :     case 4:
     216           0 :       number_of_destination_colors = 1;
     217           0 :       break;
     218             :   }
     219             : 
     220             :   // We need to convert the number of colors into the spectrum max, then convert the value from the
     221             :   // mesh to a point somewhere in the range of 0 to color_spectrum_max.
     222      150300 :   auto color_spectrum_max = (256 * number_of_destination_colors) - 1;
     223      150300 :   auto color = (unsigned int)(selection * color_spectrum_max);
     224             : 
     225             :   // Unless we specifically say some part is transparent, we want the whole image to be opaque.
     226      150300 :   auto tran = (unsigned int)(_transparency * 255);
     227             : 
     228             :   // Make sure everything is within our colorSpectrum.  If it's bigger, then we want a
     229             :   // transparent background.
     230      150300 :   if (color > color_spectrum_max)
     231             :   {
     232           0 :     color = color_spectrum_max;
     233           0 :     tran = 0;
     234             :   }
     235             : 
     236      150300 :   auto magnitude = color % 256;
     237             : 
     238      150300 :   switch (_color)
     239             :   {
     240             :     // Current color scheme: Blue->Red->Yellow->White
     241           0 :     case 1:
     242             :       // Blue->Red
     243           0 :       if (color < 256)
     244             :       {
     245           0 :         rgb[0] = magnitude;
     246           0 :         rgb[1] = 0;
     247           0 :         rgb[2] = 50; // 255 - magnitude;
     248             :       }
     249             :       // Red->Yellow
     250           0 :       else if (color < 512)
     251             :       {
     252           0 :         rgb[0] = 255;
     253           0 :         rgb[1] = magnitude;
     254           0 :         rgb[2] = 0;
     255             :       }
     256             :       // Yellow->White
     257             :       else
     258             :       {
     259           0 :         rgb[0] = 255;
     260           0 :         rgb[1] = 255;
     261           0 :         rgb[2] = magnitude;
     262             :       }
     263           0 :       break;
     264             : 
     265             :     // Color Scheme: Blue->White->Red
     266             :     // Using the RGB values found in Paraview
     267           0 :     case 2:
     268             :       // Blue->White
     269           0 :       if (color < 256)
     270             :       {
     271           0 :         rgb[0] = (int)(255.0 * (0.231373 + (0.002485 * (float)magnitude)));
     272           0 :         rgb[1] = (int)(255.0 * (0.298039 + (0.002223 * (float)magnitude)));
     273           0 :         rgb[2] = (int)(255.0 * (0.752941 + (0.000439 * (float)magnitude)));
     274             :       }
     275             :       // White->Red
     276             :       else
     277             :       {
     278           0 :         rgb[0] = (int)(255.0 * (0.865003 - (0.000624 * (float)magnitude)));
     279           0 :         rgb[1] = (int)(255.0 * (0.865003 - (0.003331 * (float)magnitude)));
     280           0 :         rgb[2] = (int)(255.0 * (0.865003 - (0.002808 * (float)magnitude)));
     281             :       }
     282           0 :       break;
     283             : 
     284             :     // Red->White->Blue
     285      150300 :     case 3:
     286             :       // Red->White
     287      150300 :       if (color < 256)
     288             :       {
     289      117039 :         rgb[0] = 255;
     290      117039 :         rgb[1] = magnitude;
     291      117039 :         rgb[2] = magnitude;
     292             :       }
     293             :       // White->Blue
     294             :       else
     295             :       {
     296       33261 :         rgb[0] = 255 - magnitude;
     297       33261 :         rgb[1] = 255 - magnitude;
     298       33261 :         rgb[2] = 255;
     299             :       }
     300      150300 :       break;
     301             : 
     302             :     // Blue->Red
     303           0 :     case 4:
     304             :       // Blue->Red
     305           0 :       rgb[0] = magnitude;
     306           0 :       rgb[1] = 0;
     307           0 :       rgb[2] = 255 - magnitude;
     308           0 :       break;
     309             :   }
     310             :   // Add any transparency.
     311      150300 :   rgb[3] = tran;
     312      150300 : }
     313             : 
     314             : void
     315         354 : PNGOutput::output()
     316             : {
     317         354 :   makeMeshFunc();
     318         354 :   _box = MeshTools::create_bounding_box(*_mesh_ptr);
     319             : 
     320             :   // Make sure this happens on processor 0
     321         354 :   if (processor_id() == 0)
     322         270 :     makePNG();
     323         354 : }
     324             : 
     325             : // Function the writes the PNG out to the appropriate filename.
     326             : void
     327         270 : PNGOutput::makePNG()
     328             : {
     329             :   // Get the max and min of the BoundingBox
     330         270 :   Point max_point = _box.max();
     331         270 :   Point min_point = _box.min();
     332             : 
     333             :   // The the total distance on the x and y axes.
     334         270 :   Real dist_x = max_point(0) - min_point(0);
     335         270 :   Real dist_y = max_point(1) - min_point(1);
     336             : 
     337             :   // Width and height for the PNG image.
     338             :   Real width;
     339             :   Real height;
     340             : 
     341             :   // Variable to record the resolution variable after normalized to work with pixels in longest
     342             :   // direction.
     343             :   Real normalized_resolution;
     344             : 
     345             :   // The longer dimension becomes the value to which we scale the other.
     346         270 :   if (dist_x > dist_y)
     347             :   {
     348          18 :     width = _resolution;
     349          18 :     height = (_resolution / dist_x) * dist_y;
     350          18 :     normalized_resolution = (((Real)_resolution) / dist_x);
     351             :   }
     352             :   else
     353             :   {
     354         252 :     height = _resolution;
     355         252 :     width = (_resolution / dist_y) * dist_x;
     356         252 :     normalized_resolution = (((Real)_resolution) / dist_y);
     357             :   }
     358             : 
     359             :   // Create the filename based on base and the test step number.
     360         270 :   std::ostringstream png_file;
     361         270 :   png_file << _file_base << "_" << std::setfill('0') << std::setw(3) << _t_step << ".png";
     362             : 
     363             :   // libpng is built on C, so by default it takes FILE*.
     364         270 :   FILE * fp = nullptr;
     365         270 :   png_structp pngp = nullptr;
     366         270 :   png_infop infop = nullptr;
     367             :   // Required depth for proper image clarity.
     368         270 :   Real depth = 8;
     369             :   // Allocate resources.
     370         270 :   std::vector<png_byte> row((width + 1) * 4);
     371             : 
     372             :   // Check if we can open and write to the file.
     373         270 :   MooseUtils::checkFileWriteable(png_file.str());
     374             : 
     375             :   // Open the file with write and bit modes.
     376         270 :   fp = fopen(png_file.str().c_str(), "wb");
     377             : 
     378         270 :   pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
     379         270 :   if (!pngp)
     380           0 :     mooseError("Failed to make the pointer string for the png.");
     381             : 
     382         270 :   infop = png_create_info_struct(pngp);
     383         270 :   if (!infop)
     384           0 :     mooseError("Failed to make an info pointer for the png.");
     385             : 
     386             :   // Initializes the IO for the png.  Needs FILE* to compile.
     387         270 :   png_init_io(pngp, fp);
     388             : 
     389             :   // Set up the PNG header.
     390         270 :   png_set_IHDR(pngp,
     391             :                infop,
     392             :                width,
     393             :                height,
     394             :                depth,
     395         270 :                (_color ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_GRAY),
     396             :                PNG_INTERLACE_NONE,
     397             :                PNG_COMPRESSION_TYPE_DEFAULT,
     398             :                PNG_FILTER_TYPE_DEFAULT);
     399             : 
     400         270 :   png_write_info(pngp, infop);
     401             : 
     402             :   // Initiallizing the point that will be used for populating the mesh values.
     403             :   // Initializing x, y, z to zero so that we don't access the point before it's
     404             :   // been set.  z = 0 for all the png's.
     405         270 :   Point pt(0, 0, 0);
     406             : 
     407             :   // Dense vector that we can pass into the _mesh_function to fill with a value for a given point.
     408         270 :   DenseVector<Number> dv(0);
     409             : 
     410             :   // Loop through to create the image.
     411        7020 :   for (Real y = max_point(1); y >= min_point(1); y -= 1. / normalized_resolution)
     412             :   {
     413        6750 :     pt(1) = y;
     414        6750 :     unsigned int index = 0;
     415      157050 :     for (Real x = min_point(0); x <= max_point(0); x += 1. / normalized_resolution)
     416             :     {
     417      150300 :       pt(0) = x;
     418      150300 :       (*_mesh_function)(pt, _time, dv, nullptr);
     419             : 
     420             :       // Determine whether to create the PNG in color or grayscale
     421      150300 :       if (_color)
     422      150300 :         setRGB(&row.data()[index * 4], applyScale(dv(0)));
     423             :       else
     424           0 :         row.data()[index] = applyScale(dv(0)) * 255;
     425             : 
     426      150300 :       index++;
     427             :     }
     428        6750 :     png_write_row(pngp, row.data());
     429             :   }
     430             : 
     431             :   // Close the file and take care of some other png end stuff.
     432         270 :   png_write_end(pngp, nullptr);
     433         270 :   if (fp != nullptr)
     434         270 :     fclose(fp);
     435         270 :   if (infop != nullptr)
     436         270 :     png_free_data(pngp, infop, PNG_FREE_ALL, -1);
     437         270 :   if (pngp != nullptr)
     438         270 :     png_destroy_write_struct(&pngp, &infop);
     439         270 : }
     440             : 
     441             : #endif

Generated by: LCOV version 1.14