32 #include "DGtal/base/Common.h"
33 #include "DGtal/helpers/StdDefs.h"
34 #include "DGtal/images/ImageContainerBySTLVector.h"
35 #include "DGtal/io/readers/GenericReader.h"
36 #include "DGtal/io/writers/GenericWriter.h"
37 #include "DGtal/io/writers/PPMWriter.h"
38 #include "DGtal/images/ImageSelector.h"
39 #include "DGtal/io/readers/PointListReader.h"
40 #include "DGtal/images/ConstImageAdapter.h"
41 #include "DGtal/kernel/BasicPointFunctors.h"
43 #include "DGtal/io/colormaps/GrayscaleColorMap.h"
49 using namespace DGtal;
97 template<
typename TImage,
typename TImageVector>
99 computerBasicNormalsFromHeightField(
const TImage &anHeightMap, TImageVector &vectorField)
101 for(
typename TImage::Domain::ConstIterator it = anHeightMap.domain().begin();
102 it != anHeightMap.domain().end(); it++){
103 if(anHeightMap.domain().isInside(*it+Z2i::Point::diagonal(1))&&
104 anHeightMap.domain().isInside(*it-Z2i::Point::diagonal(1))){
105 double dx = (anHeightMap(*it-Z2i::Point(1,0))-anHeightMap(*it+Z2i::Point(1,0)))/2.0;
106 double dy = (anHeightMap(*it-Z2i::Point(0,1))-anHeightMap(*it+Z2i::Point(0,1)))/2.0;
107 Z3i::RealPoint n (dx, dy, 1);
109 vectorField.setValue(*it,n);
116 template<
typename TImageVector>
118 importNormals(std::string file, TImageVector &vectorField)
120 std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
121 for(
unsigned int i = 0; i< vp.size(); i=i+2){
122 Z3i::RealPoint p = vp.at(i);
123 Z3i::RealPoint q = vp.at(i+1);
124 Z3i::RealPoint n = (q-p)/(p-q).norm();
125 vectorField.setValue(Z2i::Point(p[0], p[1]),n);
130 template<
typename TImage2D,
typename TPo
int3D >
131 struct LambertianShadindFunctor{
132 LambertianShadindFunctor(
const TPoint3D &aLightSourceDir ):
133 myLightSourceDirection(aLightSourceDir/aLightSourceDir.norm()){}
136 unsigned int operator()(
const TPoint3D &aNormal)
const
138 int intensity = aNormal.dot(myLightSourceDirection)*std::numeric_limits<typename TImage2D::Value>::max();
139 return intensity>0? intensity:0;
141 TPoint3D myLightSourceDirection;
146 template<
typename TImage2D,
typename TPo
int3D >
147 struct LambertianShadindFunctorAllDirections{
148 LambertianShadindFunctorAllDirections(
const TPoint3D &aLightSourcePosition ):
149 myLightSourcePosition(aLightSourcePosition){}
152 unsigned int operator()(
const TPoint3D &aNormal,
const Z2i::Point &aPoint,
const double h)
const
156 Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
157 l = -posL+myLightSourcePosition;
159 int intensity = aNormal.dot(l)*std::numeric_limits<typename TImage2D::Value>::max();
160 return intensity>0? intensity:0;
162 TPoint3D myLightSourcePosition;
170 template<
typename TImage2D,
typename TPo
int3D >
171 struct SpecularNayarShadindFunctor{
172 SpecularNayarShadindFunctor(
const TPoint3D &lightSourceDirection,
const double kld,
173 const double kls,
const double sigma ):
174 myLightSourceDirection(lightSourceDirection/lightSourceDirection.norm()),
175 myKld(kld), myKls(kls), mySigma(sigma){}
178 unsigned int operator()(
const TPoint3D &aNormal)
const {
179 double lambertianIntensity = std::max(aNormal.dot(myLightSourceDirection), 0.0);
180 double alpha = acos(((myLightSourceDirection+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
181 double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
182 double resu = myKld*lambertianIntensity+myKls*specularIntensity;
184 resu = std::max(resu, 0.0);
185 resu = std::min(resu, 1.0);
186 return resu*std::numeric_limits<typename TImage2D::Value>::max();
189 TPoint3D myLightSourceDirection;
190 double myKld, myKls, mySigma;
196 template<
typename TImage2D,
typename TPo
int3D >
197 struct SpecularNayarShadindFunctorAllDirections{
198 SpecularNayarShadindFunctorAllDirections(
const TPoint3D &lightSourcePosition,
const double kld,
199 const double kls,
const double sigma ):
200 myLightSourcePosition(lightSourcePosition),
201 myKld(kld), myKls(kls), mySigma(sigma){}
204 unsigned int operator()(
const TPoint3D &aNormal,
const Z2i::Point &aPoint,
const double h)
const {
206 Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
207 l = -posL+myLightSourcePosition;
210 double lambertianIntensity = std::max(aNormal.dot(l), 0.0);
211 double alpha = acos(((l+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
212 double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
213 double resu = myKld*lambertianIntensity+myKls*specularIntensity;
215 resu = std::max(resu, 0.0);
216 resu = std::min(resu, 1.0);
217 return resu*std::numeric_limits<typename TImage2D::Value>::max();
220 TPoint3D myLightSourcePosition;
221 double myKld, myKls, mySigma;
228 template<
typename TImage2D,
typename TPo
int3D >
229 struct ImageMapReflectance{
230 ImageMapReflectance(
const std::string &filename): myImageMap (PPMReader<TImage2D>::importPPM(filename))
233 myCenterPoint = (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())/2;
234 myImageRadius = min((myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[1], (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[0])/2;
238 unsigned int operator()(
const TPoint3D &aNormal)
const
240 Z2i::Point p(aNormal[0]*myImageRadius,aNormal[1]*myImageRadius ) ;
242 if(myImageMap.domain().isInside(p)){
243 return myImageMap(p);
245 return myImageMap(Z2i::Point(0,0,0));
249 Z2i::Point myCenterPoint;
250 unsigned int myImageRadius;
255 Color operator()(
const unsigned int & aValue )
const{
256 return DGtal::Color(aValue);
262 int main(
int argc,
char** argv )
265 typedef ImageSelector < Z2i::Domain, unsigned char>::Type Image2D;
267 typedef ImageContainerBySTLVector < Z2i::Domain, Z3i::RealPoint> Image2DNormals;
273 std::string inputFileName;
274 std::string outputFileName {
"result.pgm"};
275 std::string normalFileName {
""};
276 double lx, ly, lz, px, py, pz;
277 bool usingAllDirectionLightSource =
false;
278 std::vector<double> specularModel;
279 std::string reflectanceMap;
280 std::vector<double> lDir;
281 std::vector<double> lPos;
284 app.description(
"Render a 2D heightfield image into a shading image. You can choose between lambertian model (diffuse reflectance) and specular model (Nayar reflectance model). You can also choose between a single directional light source (using --lightDir option) or use light source which emits in all direction (by specifying the light source position with --lightPos} option). Another rendering mode is given from a bitmap reflectance map which represents the rendering for a normal vector value (mapped according the x/y coordinates).\nExample:\n ${DGtal}/examples/samples/bunnyHeightField.pgm shading.pgm --lPos 10.0 -120.0 550.0 --importNormal ${DGtal}/examples/samples/bunnyHeightField_normals.sdp -s 1.0 0.2 0.8 \n");
285 app.add_option(
"-i,--input,1", inputFileName,
"input heightfield file (2D image).")
286 ->check(CLI::ExistingFile)
288 app.add_option(
"-o,--output,2", outputFileName,
"output image.");
289 app.add_option(
"--importNormal", normalFileName,
"import normals from file.");
291 app.add_option(
"--lightDir,--lDir,--ld", lDir,
"light source direction: lx ly lz.")
293 app.add_option(
"--lightPos,--lPos,--lp", lPos,
"light source position: px py pz.")
295 app.add_option(
"-s,--specularModel", specularModel,
"use specular Nayar model with 3 param Kdiff, Kspec, sigma." )
297 app.add_option(
"-r,--reflectanceMap",reflectanceMap,
"specify a image as reflectance map.")
298 ->check(CLI::ExistingFile);
301 app.get_formatter()->column_width(40);
302 CLI11_PARSE(app, argc, argv);
313 else if(lPos.size() == 3)
318 usingAllDirectionLightSource =
true;
320 else if (reflectanceMap ==
"")
322 trace.error() <<
"You need to specify either the light source direction or position (if you use a all directions model)." << std::endl;
326 LambertianShadindFunctor<Image2D, Z3i::RealPoint> lShade (Z3i::RealPoint(lx,ly,lz));
327 LambertianShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lShadePosD (Z3i::RealPoint(px ,py, pz));
328 SpecularNayarShadindFunctor<Image2D, Z3i::RealPoint> lSpecular (Z3i::RealPoint(lx,ly,lz), 0, 0, 0);
329 SpecularNayarShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lSpecularPosD (Z3i::RealPoint(px,py,pz), 0, 0, 0);
332 bool useSpecular =
false;
333 if(specularModel.size() == 3){
335 lSpecular.myKld = specularModel[0];
336 lSpecular.myKls = specularModel[1];
337 lSpecular.mySigma = specularModel[2];
338 lSpecularPosD.myKld = specularModel[0];
339 lSpecularPosD.myKls = specularModel[1];
340 lSpecularPosD.mySigma = specularModel[2];
341 if(specularModel[2]==0.0)
343 trace.error()<<
"a 0 value for sigma is not possible in the Nayar model, please change it. "<< std::endl;
350 trace.info() <<
"Reading input file " << inputFileName ;
351 Image2D inputImage = DGtal::GenericReader<Image2D>::import(inputFileName);
352 Image2DNormals vectNormals (inputImage.domain());
353 Image2D result (inputImage.domain());
354 if(normalFileName !=
""){
355 importNormals(normalFileName, vectNormals);
357 computerBasicNormalsFromHeightField(inputImage, vectNormals);
359 if(reflectanceMap !=
"")
361 ImageMapReflectance<Image2D, Z3i::RealPoint> lMap(reflectanceMap);
362 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
363 it != inputImage.domain().end(); it++){
364 if(reflectanceMap !=
"")
366 result.setValue(*it, lMap(vectNormals(*it)));
370 PPMWriter<Image2D, IdColor >::exportPPM(outputFileName, result,
id);
375 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
376 it != inputImage.domain().end(); it++){
377 if(usingAllDirectionLightSource)
379 result.setValue(*it, useSpecular? lSpecularPosD(vectNormals(*it), *it, inputImage(*it)):
380 lShadePosD(vectNormals(*it), *it, inputImage(*it)));
384 result.setValue(*it, useSpecular? lSpecular(vectNormals(*it)):lShade(vectNormals(*it)));
388 result >> outputFileName;