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
|