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"
122template<
typename TImage,
typename TImageVector>
124computerBasicNormalsFromHeightField(
const TImage &anHeightMap, TImageVector &vectorField,
125 bool invertN =
false)
127 for(
typename TImage::Domain::ConstIterator it = anHeightMap.domain().begin();
128 it != anHeightMap.domain().end(); it++){
129 if(anHeightMap.domain().isInside(*it+Z2i::Point::diagonal(1))&&
130 anHeightMap.domain().isInside(*it-Z2i::Point::diagonal(1))){
131 double dx = (anHeightMap(*it-Z2i::Point(1,0))-anHeightMap(*it+Z2i::Point(1,0)))/2.0;
132 double dy = (anHeightMap(*it-Z2i::Point(0,1))-anHeightMap(*it+Z2i::Point(0,1)))/2.0;
133 Z3i::RealPoint n (dx, dy, 1);
135 vectorField.setValue(*it,invertN? -n : n);
142template<
typename TImageVector>
144importNormals(std::string file, TImageVector &vectorField,
145 bool invertN =
false)
147 std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
148 trace.info() <<
"import done: " << vp.size() << std::endl;
149 for(
unsigned int i = 0; i< vp.size()-1; i=i+2){
150 Z3i::RealPoint p = vp.at(i);
151 Z3i::RealPoint q = vp.at(i+1);
152 Z3i::RealPoint n = (q-p)/(p-q).norm();
153 vectorField.setValue(Z2i::Point(p[0], p[1]),invertN? -n : n);
159template<
typename TImageVector>
161importNormalsOrdDir(std::string file, TImageVector &vectorField,
162 unsigned int width,
unsigned int height,
bool invertN =
false)
164 std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
166 for(
unsigned int i = 0; i< vp.size()-1; i++){
167 Z2i::Point p ( i-(width*floor((i/width))), i/width);
168 Z3i::RealPoint n = vp[i];
169 vectorField.setValue(p,invertN? -n : n);
176template<
typename TImage2D,
typename TPo
int3D >
177struct LambertianShadindFunctor{
178 LambertianShadindFunctor(
const TPoint3D &aLightSourceDir ):
179 myLightSourceDirection(aLightSourceDir/aLightSourceDir.norm()){}
182 unsigned int operator()(
const TPoint3D &aNormal)
const
184 int intensity = aNormal.dot(myLightSourceDirection)*std::numeric_limits<typename TImage2D::Value>::max();
185 return intensity>0? intensity:0;
187 TPoint3D myLightSourceDirection;
192template<
typename TImage2D,
typename TPo
int3D >
193struct LambertianShadindFunctorAllDirections{
194 LambertianShadindFunctorAllDirections(
const TPoint3D &aLightSourcePosition ):
195 myLightSourcePosition(aLightSourcePosition){}
198 unsigned int operator()(
const TPoint3D &aNormal,
const Z2i::Point &aPoint,
const double h)
const
202 Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
203 l = -posL+myLightSourcePosition;
205 int intensity = aNormal.dot(l)*std::numeric_limits<typename TImage2D::Value>::max();
206 return intensity>0? intensity:0;
208 TPoint3D myLightSourcePosition;
216template<
typename TImage2D,
typename TPo
int3D >
217struct SpecularNayarShadindFunctor{
218 SpecularNayarShadindFunctor(
const TPoint3D &lightSourceDirection,
const double kld,
219 const double kls,
const double sigma ):
220 myLightSourceDirection(lightSourceDirection/lightSourceDirection.norm()),
221 myKld(kld), myKls(kls), mySigma(sigma){}
224 unsigned int operator()(
const TPoint3D &aNormal)
const {
225 double lambertianIntensity = std::max(aNormal.dot(myLightSourceDirection), 0.0);
226 double alpha = acos(((myLightSourceDirection+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
227 double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
228 double resu = myKld*lambertianIntensity+myKls*specularIntensity;
230 resu = std::max(resu, 0.0);
231 resu = std::min(resu, 1.0);
232 return resu*std::numeric_limits<typename TImage2D::Value>::max();
235 TPoint3D myLightSourceDirection;
236 double myKld, myKls, mySigma;
242template<
typename TImage2D,
typename TPo
int3D >
243struct SpecularNayarShadindFunctorAllDirections{
244 SpecularNayarShadindFunctorAllDirections(
const TPoint3D &lightSourcePosition,
const double kld,
245 const double kls,
const double sigma ):
246 myLightSourcePosition(lightSourcePosition),
247 myKld(kld), myKls(kls), mySigma(sigma){}
250 unsigned int operator()(
const TPoint3D &aNormal,
const Z2i::Point &aPoint,
const double h)
const {
252 Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
253 l = -posL+myLightSourcePosition;
256 double lambertianIntensity = std::max(aNormal.dot(l), 0.0);
257 double alpha = acos(((l+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
258 double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
259 double resu = myKld*lambertianIntensity+myKls*specularIntensity;
261 resu = std::max(resu, 0.0);
262 resu = std::min(resu, 1.0);
263 return resu*std::numeric_limits<typename TImage2D::Value>::max();
266 TPoint3D myLightSourcePosition;
267 double myKld, myKls, mySigma;
274template<
typename TImage2D,
typename TPo
int3D >
275struct ImageMapReflectance{
276 ImageMapReflectance(
const std::string &filename): myImageMap (PPMReader<TImage2D>::importPPM(filename))
279 myCenterPoint = (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())/2;
280 myImageRadius = min((myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[1], (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[0])/2;
284 unsigned int operator()(
const TPoint3D &aNormal)
const
286 Z2i::Point p(aNormal[0]*myImageRadius,aNormal[1]*myImageRadius ) ;
288 if(myImageMap.domain().isInside(p)){
289 return myImageMap(p);
291 return myImageMap(Z2i::Point(0,0,0));
295 Z2i::Point myCenterPoint;
296 unsigned int myImageRadius;
302 Color operator()(
const unsigned int & aValue )
const{
303 return DGtal::Color(aValue);
310colorFromHSB(
double h,
double saturation,
double value){
312 DGtal::Color::HSVtoRGB(r, g, b, h,saturation, value);
313 return DGtal::Color(r*255.0,g*255.0,b*255.0);
318int main(
int argc,
char** argv )
320 typedef ImageContainerBySTLVector < Z2i::Domain, unsigned int> Image2DC;
321 typedef ImageSelector < Z2i::Domain, unsigned char>::Type Image2D;
322 typedef ImageContainerBySTLVector < Z2i::Domain, Z3i::RealPoint> Image2DNormals;
328 std::string inputFileName;
329 std::string outputFileName {
"result.pgm"};
330 std::string normalFileName {
""};
331 double lx, ly, lz, px, py, pz;
332 bool usingAllDirectionLightSource =
false;
333 bool useOrderedImportNormal =
false;
334 bool hsvShading =
false;
335 bool normalMap =
false;
336 bool invertNormals =
false;
337 std::vector<double> specularModel;
338 std::string reflectanceMap;
339 std::vector<double> lDir = {0, 0, 1};
340 std::vector<double> lPos;
341 std::vector<unsigned int> domain;
343 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 heightfield2shading ${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"
344 "Other example: heightfield2shading ${DGtal}/examples/samples/bunnyHeightField.pgm shading.ppm --importNormal ${DGtal}/examples/samples/bunnyHeightField_normals.sdp --hsvShading\n");
345 auto opt1 = app.add_option(
"-i,--input,1", inputFileName,
"input heightfield file (2D image).")
346 ->check(CLI::ExistingFile);
347 auto domOpt = app.add_option(
"--domain,-d", domain ,
"specify the domain (required when normal are imported and if --inout is not given).")
349 app.add_option(
"-o,--output,2", outputFileName,
"output image.");
350 auto impNOpt = app.add_option(
"--importNormal", normalFileName,
"import normals from file.");
351 app.add_flag(
"--orderedNormalsImport",useOrderedImportNormal,
"Use ordered normals." );
352 app.add_option(
"--lightDir,--lDir,--ld", lDir,
"light source direction: lx ly lz.")
354 app.add_option(
"--lightPos,--lPos,--lp", lPos,
"light source position: px py pz.")
356 app.add_option(
"-s,--specularModel", specularModel,
"use specular Nayar model with 3 param Kdiff, Kspec, sigma." )
358 app.add_option(
"-r,--reflectanceMap",reflectanceMap,
"specify a image as reflectance map.")
359 ->check(CLI::ExistingFile);
360 app.add_flag(
"--hsvShading", hsvShading,
"use shading with HSV shading (given from the normal vector)");
361 app.add_flag(
"--normalMap", normalMap,
"generates normal map.");
362 app.add_flag(
"--invertNormals,-v", invertNormals,
"invert normal orientations.");
363 app.get_formatter()->column_width(40);
364 CLI11_PARSE(app, argc, argv);
367 if(! *opt1 && !(*domOpt && *impNOpt ) ){
368 trace.error() <<
"You need either set input file (--input) or use a domain (--domain) with the --importNormal option." << std::endl;
379 else if(lPos.size() == 3)
384 usingAllDirectionLightSource =
true;
386 else if (reflectanceMap ==
"" && ! hsvShading && !normalMap)
388 trace.error() <<
"You need to specify either the light source direction or position (if you use a all directions model)." << std::endl;
392 LambertianShadindFunctor<Image2D, Z3i::RealPoint> lShade (Z3i::RealPoint(lx,ly,lz));
393 LambertianShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lShadePosD (Z3i::RealPoint(px ,py, pz));
394 SpecularNayarShadindFunctor<Image2D, Z3i::RealPoint> lSpecular (Z3i::RealPoint(lx,ly,lz), 0, 0, 0);
395 SpecularNayarShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lSpecularPosD (Z3i::RealPoint(px,py,pz), 0, 0, 0);
398 bool useSpecular =
false;
399 if(specularModel.size() == 3){
401 lSpecular.myKld = specularModel[0];
402 lSpecular.myKls = specularModel[1];
403 lSpecular.mySigma = specularModel[2];
404 lSpecularPosD.myKld = specularModel[0];
405 lSpecularPosD.myKls = specularModel[1];
406 lSpecularPosD.mySigma = specularModel[2];
407 if(specularModel[2]==0.0)
409 trace.error()<<
"a 0 value for sigma is not possible in the Nayar model, please change it. "<< std::endl;
416 Image2D inputImage(Z2i::Domain(Z2i::Point(0,0), Z2i::Point(0,0) ));
417 if (inputFileName !=
"") {
418 trace.info() <<
"Reading input file " << inputFileName ;
419 inputImage = DGtal::GenericReader<Image2D>::import(inputFileName);
420 trace.info() <<
"[done]" << std::endl;
423 inputImage = Image2D(Z2i::Domain(Z2i::Domain(Z2i::Point(0,0),
424 Z2i::Point(domain[0],domain[1]) )));
427 Image2DNormals vectNormals (inputImage.domain());
428 Image2D result (inputImage.domain());
429 Image2DC resultC (inputImage.domain());
431 if(normalFileName !=
""){
432 trace.info() <<
"Import normal file " << inputFileName << vectNormals.domain();
433 if (useOrderedImportNormal)
435 importNormalsOrdDir(normalFileName, vectNormals,
436 inputImage.domain().upperBound()[0]+1,
437 inputImage.domain().upperBound()[1]+1, invertNormals);
441 importNormals(normalFileName, vectNormals, invertNormals);
443 trace.info() <<
"[done]" << std::endl;
447 computerBasicNormalsFromHeightField(inputImage, vectNormals, invertNormals);
451 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
452 it != inputImage.domain().end(); it++){
453 auto n = vectNormals(*it);
454 double sat = 1.0*( sin(acos(Z3i::RealPoint(0.0,0.0,1.0).dot(n))));
456 double hue = ((int)(((2.0*M_PI+atan2(Z3i::RealPoint(0.0,1.0,0.0).dot(n),
457 Z3i::RealPoint(1.0,0.0,0.0).dot(n)))/(2.0*M_PI))*360.0+100))%360;
458 DGtal::uint32_t colCode = colorFromHSB(hue, sat, value).getRGB();
459 resultC.setValue(*it, colCode);
462 PPMWriter<Image2DC, IdColor >::exportPPM(outputFileName, resultC,
id);
466 DGtal::functors::Rescaling<double, unsigned int> rgRescale (-1.0, 1.0, 0, 255);
467 DGtal::functors::Rescaling<double, unsigned int> bRescale (0.0, -1.0, 128, 255);
468 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
469 it != inputImage.domain().end(); it++){
470 auto n = vectNormals(*it);
471 DGtal::Color c (rgRescale(n[0]), rgRescale(n[1]), bRescale(n[2]) );
472 resultC.setValue(*it, c.getRGB());
475 PPMWriter<Image2DC, IdColor >::exportPPM(outputFileName, resultC,
id);
477 else if(reflectanceMap !=
"")
479 ImageMapReflectance<Image2D, Z3i::RealPoint> lMap(reflectanceMap);
480 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
481 it != inputImage.domain().end(); it++){
482 result.setValue(*it, lMap(vectNormals(*it)));
485 PPMWriter<Image2D, IdColor >::exportPPM(outputFileName, result,
id);
490 for(
typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
491 it != inputImage.domain().end(); it++){
492 if(usingAllDirectionLightSource)
494 result.setValue(*it, useSpecular? lSpecularPosD(vectNormals(*it), *it, inputImage(*it)):
495 lShadePosD(vectNormals(*it), *it, inputImage(*it)));
499 result.setValue(*it, useSpecular? lSpecular(vectNormals(*it)):lShade(vectNormals(*it)));
503 result >> outputFileName;