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
|