1 /*
2 * This file is part of Filmulator.
3 *
4 * Copyright 2013 Omer Mano and Carlo Vaccari
5 *
6 * Filmulator is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Filmulator is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Filmulator. If not, see <http://www.gnu.org/licenses/>
18 */
19 #include "imagePipeline.h"
20 #include <algorithm>
21 #include <stdio.h>
22 #include <unistd.h>
23
24 //Function-------------------------------------------------------------------------
filmulate(matrix<float> & input_image,matrix<float> & output_density,ParameterManager * paramManager,ImagePipeline * pipeline)25 bool ImagePipeline::filmulate(matrix<float> &input_image,
26 matrix<float> &output_density,
27 ParameterManager * paramManager,
28 ImagePipeline * pipeline)
29 {
30 FilmParams filmParam;
31 AbortStatus abort;
32 Valid valid;
33 std::tie(valid, abort, filmParam) = paramManager->claimFilmParams();
34 if(abort == AbortStatus::restart)
35 {
36 return true;
37 }
38
39 //Extract parameters from struct
40 float initial_developer_concentration = filmParam.initialDeveloperConcentration;
41 float reservoir_thickness = filmParam.reservoirThickness;
42 float active_layer_thickness = filmParam.activeLayerThickness;
43 float crystals_per_pixel = filmParam.crystalsPerPixel;
44 float initial_crystal_radius = filmParam.initialCrystalRadius;
45 float initial_silver_salt_density = filmParam.initialSilverSaltDensity;
46 float developer_consumption_const = filmParam.developerConsumptionConst;
47 float crystal_growth_const = filmParam.crystalGrowthConst;
48 float silver_salt_consumption_const = filmParam.silverSaltConsumptionConst;
49 float total_development_time = filmParam.totalDevelopmentTime;
50 int agitate_count = filmParam.agitateCount;
51 int development_steps = filmParam.developmentSteps;
52 float film_area = filmParam.filmArea;
53 float sigma_const = filmParam.sigmaConst;
54 float layer_mix_const = filmParam.layerMixConst;
55 float layer_time_divisor = filmParam.layerTimeDivisor;
56 float rolloff_boundary = filmParam.rolloffBoundary;
57 float toe_boundary = filmParam.toeBoundary;
58
59 //Set up timers
60 struct timeval initialize_start, development_start, develop_start,
61 diffuse_start, agitate_start, layer_mix_start;
62 double develop_dif = 0, diffuse_dif = 0, agitate_dif = 0, layer_mix_dif= 0;
63 gettimeofday(&initialize_start,NULL);
64
65 int nrows = (int) input_image.nr();
66 int ncols = (int) input_image.nc()/3;
67 int npix = nrows*ncols;
68
69 //Now we activate some of the crystals on the film. This is literally
70 //akin to exposing film to light.
71 matrix<float> active_crystals_per_pixel = input_image;
72 exposure(active_crystals_per_pixel, crystals_per_pixel, rolloff_boundary, toe_boundary);
73 //We set the crystal radius to a small seed value for each color.
74 matrix<float>& crystal_radius = output_density;
75 crystal_radius.set_size(nrows,ncols*3);
76 crystal_radius = initial_crystal_radius;
77
78 //All layers share developer, so we only make it the original image size.
79 matrix<float> developer_concentration;
80 developer_concentration.set_size(nrows,ncols);
81 developer_concentration = initial_developer_concentration;
82
83 //Each layer gets its own silver salt which will feed crystal growth.
84 matrix<float> silver_salt_density;
85 silver_salt_density.set_size(nrows,ncols*3);
86 silver_salt_density = initial_silver_salt_density;
87
88 //Now, we set up the reservoir.
89 //Because we don't want the film area to influence the brightness, we
90 // increase the reservoir size in proportion.
91 #define FILMSIZE 864;//36x24mm
92 reservoir_thickness *= film_area/FILMSIZE;
93 float reservoir_developer_concentration = initial_developer_concentration;
94
95 //This is a value used in diffuse to set the length scale.
96 float pixels_per_millimeter = sqrt(npix/film_area);
97
98 //Here we do some math for the control logic for the differential
99 //equation approximation computations.
100 float timestep = total_development_time/development_steps;
101 int agitate_period;
102 if(agitate_count > 0)
103 {
104 agitate_period = floor(development_steps/agitate_count);
105 }
106 else
107 {
108 agitate_period = 3*development_steps;
109 }
110 int half_agitate_period = floor(agitate_period/2);
111
112 tout << "Initialization time: " << timeDiff(initialize_start)
113 << " seconds" << endl;
114 gettimeofday(&development_start,NULL);
115
116 //Now we begin the main development/diffusion loop, which approximates the
117 //differential equation of film development.
118 for(int i = 0; i <= development_steps; i++)
119 {
120 //Check for cancellation
121 abort = paramManager->claimFilmAbort();
122 if(abort == AbortStatus::restart)
123 {
124 return true;
125 }
126
127 //Updating for starting the development simulation. Valid is one too high here.
128 pipeline->updateProgress(Valid::partfilmulation, float(i)/float(development_steps));
129
130 gettimeofday(&develop_start,NULL);
131
132 //This is where we perform the chemical reaction part.
133 //The crystals grow.
134 //The developer in the active layer is consumed.
135 //So is the silver salt in the film.
136 // The amount consumed increases as the crystals grow larger.
137 //Because the developer and silver salts are consumed in bright regions,
138 // this reduces the rate at which they grow. This gives us global
139 // contrast reduction.
140 develop(crystal_radius,crystal_growth_const,active_crystals_per_pixel,
141 silver_salt_density,developer_concentration,
142 active_layer_thickness,developer_consumption_const,
143 silver_salt_consumption_const,timestep);
144
145 develop_dif += timeDiff(develop_start);
146 gettimeofday(&diffuse_start,NULL);
147
148 //Check for cancellation
149 abort = paramManager->claimFilmAbort();
150 if(abort == AbortStatus::restart)
151 {
152 return true;
153 }
154
155 //Updating for starting the diffusion simulation. Valid is one too high here.
156 pipeline->updateProgress(Valid::partfilmulation, float(i)/float(development_steps));
157
158 //Now, we are going to perform the diffusion part.
159 //Here we mix the layer among itself, which grants us the
160 // local contrast increases.
161 diffuse(developer_concentration,
162 sigma_const,
163 pixels_per_millimeter,
164 timestep);
165 // diffuse_short_convolution(developer_concentration,
166 // sigma_const,
167 // pixels_per_millimeter,
168 // timestep);
169 // diffuse_resize_iir(developer_concentration,
170 // sigma_const,
171 // pixels_per_millimeter,
172 // timestep);
173
174 diffuse_dif += timeDiff(diffuse_start);
175
176 gettimeofday(&layer_mix_start,NULL);
177 //This performs mixing between the active layer adjacent to the film
178 // and the reservoir.
179 //This keeps the effects from getting too crazy.
180 layer_mix(developer_concentration,
181 active_layer_thickness,
182 reservoir_developer_concentration,
183 reservoir_thickness,
184 layer_mix_const,
185 layer_time_divisor,
186 pixels_per_millimeter,
187 timestep);
188
189 layer_mix_dif += timeDiff(layer_mix_start);
190
191 gettimeofday(&agitate_start,NULL);
192
193 //I want agitation to only occur in the middle of development, not
194 //at the very beginning or the ends. So, I add half the agitate
195 //period to the current cycle count.
196 if((i+half_agitate_period) % agitate_period ==0)
197 agitate(developer_concentration, active_layer_thickness,
198 reservoir_developer_concentration, reservoir_thickness,
199 pixels_per_millimeter);
200
201 agitate_dif += timeDiff(agitate_start);
202 }
203 tout << "Development time: " <<timeDiff(development_start)<< " seconds" << endl;
204 tout << "Develop time: " << develop_dif << " seconds" << endl;
205 tout << "Diffuse time: " << diffuse_dif << " seconds" << endl;
206 tout << "Layer mix time: " << layer_mix_dif << " seconds" << endl;
207 tout << "Agitate time: " << agitate_dif << " seconds" << endl;
208
209 //Now we compute the density (opacity) of the film.
210 //We assume that overlapping crystals or dye clouds are
211 //nonexistent. It works okay, for now...
212 //The output is crystal_radius^2 * active_crystals_per_pixel
213 struct timeval mult_start;
214 gettimeofday(&mult_start,NULL);
215
216 abort = paramManager->claimFilmAbort();
217 if(abort == AbortStatus::restart)
218 {
219 return true;
220 }
221
222 const int numRows = crystal_radius.nr();
223 const int numCols = crystal_radius.nc();
224
225 #pragma omp parallel for
226 for (int i = 0; i < numRows; ++i) {
227 for (int j = 0; j < numCols; ++j) {
228 output_density(i, j) = crystal_radius(i, j) * crystal_radius(i, j) * active_crystals_per_pixel(i, j);
229 }
230 }
231 tout << "Output density time: "<<timeDiff(mult_start) << endl;
232 #ifdef DOUT
233 debug_out.close();
234 #endif
235 return false;
236 }
237
238