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

Generated by: LCOV version 1.14