Panda3D
 All Classes Functions Variables Enumerations
imageTransformColors.cxx
00001 // Filename: imageTransformColors.cxx
00002 // Created by:  drose (25Mar09)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "imageTransformColors.h"
00016 #include "string_utils.h"
00017 #include "pystub.h"
00018 #include "pnmImage.h"
00019 #include <math.h>
00020 
00021 ////////////////////////////////////////////////////////////////////
00022 //     Function: ImageTransformColors::Constructor
00023 //       Access: Public
00024 //  Description:
00025 ////////////////////////////////////////////////////////////////////
00026 ImageTransformColors::
00027 ImageTransformColors() {
00028   set_program_description
00029     ("This program can apply a global color transform to all of the "
00030      "pixels in an image, or in a series of images.  This can be used, "
00031      "for instance, to increase or decrease the dynamic range; or to "
00032      "rotate the hue; or to reduce the saturation of colors in the image.\n\n"
00033      
00034      "Each parameter is encoded in a 4x4 matrix, which modifies the R, G, B "
00035      "colors of the image (the alpha values, if any, are not affected).  "
00036      "RGB values are clamped at 0 and 1 after the operation.  "
00037      "Multiple parameters are composed together in the order in which they "
00038      "are listed.");
00039 
00040   add_option
00041     ("hls", "", 0,
00042      "Specifies that all of the matrix operations are performed in HLS "
00043      "space, instead of the default RGB space.  In this mode, the first "
00044      "component controls hue, the second controls lightness, and the third "
00045      "controls saturation.",
00046      &ImageTransformColors::dispatch_none, &_hls, NULL);
00047 
00048   add_option
00049     ("range", "min,max", 0,
00050      "Compresses the overall dynamic range from 0,1 to min,max.  If min,max "
00051      "exceed 0,1, the dynamic range is expanded.  This doesn't make sense in "
00052      "HLS mode.",
00053      &ImageTransformColors::dispatch_range, NULL, &_mat);
00054 
00055   add_option
00056     ("scale", "r,g,b", 0,
00057      "Scales the r,g,b components by the indicated values.  In HLS mode, "
00058      "the scale is applied to the h,l,s components.",
00059      &ImageTransformColors::dispatch_scale, NULL, &_mat);
00060 
00061   add_option
00062     ("add", "r,g,b", 0,
00063      "Adds the indicated values to the r,g,b components.  In HLS mode, "
00064      "the sum is applied to the h,l,s components.",
00065      &ImageTransformColors::dispatch_add, NULL, &_mat);
00066 
00067   add_option
00068     ("mat4", "m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33",
00069      0, "Defines an arbitrary 4x4 RGB matrix.",
00070      &ImageTransformColors::dispatch_mat4, NULL, &_mat);
00071 
00072   add_option
00073     ("mat3", "m00,m01,m02,m10,m11,m12,m20,m21,m22", 0, 
00074      "Defines an arbitrary 3x3 RGB matrix.",
00075      &ImageTransformColors::dispatch_mat3, NULL, &_mat);
00076 
00077   add_option
00078     ("o", "filename", 50,
00079      "Specify the filename to which the resulting image file will be written.  "
00080      "This is only valid when there is only one input image file on the command "
00081      "line.  If you want to process multiple files simultaneously, you must "
00082      "use either -d or -inplace.",
00083      &ImageTransformColors::dispatch_filename, &_got_output_filename, &_output_filename);
00084 
00085   add_option
00086     ("d", "dirname", 50,
00087      "Specify the name of the directory in which to write the resulting image "
00088      "files.  If you are processing only one image file, this may be omitted "
00089      "in lieu of the -o option.  If you are processing multiple image files, "
00090      "this may be omitted only if you specify -inplace instead.",
00091      &ImageTransformColors::dispatch_filename, &_got_output_dirname, &_output_dirname);
00092 
00093   add_option
00094     ("inplace", "", 50,
00095      "If this option is given, the input image files will be rewritten in "
00096      "place with the results.  This obviates the need to specify -d "
00097      "for an output directory; however, it's risky because the original "
00098      "input image files are lost.",
00099      &ImageTransformColors::dispatch_none, &_inplace);
00100   
00101   _mat = LMatrix4d::ident_mat();
00102 }
00103 
00104 ////////////////////////////////////////////////////////////////////
00105 //     Function: ImageTransformColors::run
00106 //       Access: Public
00107 //  Description:
00108 ////////////////////////////////////////////////////////////////////
00109 void ImageTransformColors::
00110 run() {
00111   _mat.write(nout, 0);
00112   nout << "\n";
00113 
00114   Filenames::iterator fi;
00115   for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) {
00116     const Filename &source_filename = (*fi);
00117     nout << source_filename << "\n";
00118     PNMImage image;
00119     if (!image.read(source_filename)) {
00120       nout << "Couldn't read " << source_filename << "; ignoring.\n";
00121       continue;
00122     }
00123 
00124     process_image(image);
00125 
00126     Filename output_filename = get_output_filename(source_filename);
00127     if (!image.write(output_filename)) {
00128       nout << "Couldn't write " << output_filename << "; ignoring.\n";
00129     }
00130   }      
00131 }
00132 
00133 ////////////////////////////////////////////////////////////////////
00134 //     Function: ImageTransformColors::dispatch_mat4
00135 //       Access: Protected, Static
00136 //  Description: Takes a series of 16 numbers as a 4x4 matrix.
00137 ////////////////////////////////////////////////////////////////////
00138 bool ImageTransformColors::
00139 dispatch_mat4(const string &opt, const string &arg, void *var) {
00140   LMatrix4d &orig = *(LMatrix4d *)var;
00141 
00142   vector_string words;
00143   tokenize(arg, words, ",");
00144 
00145   LMatrix4d mat;
00146   bool okflag = false;
00147   if (words.size() == 16) {
00148     okflag =
00149       string_to_double(words[0], mat[0][0]) &&
00150       string_to_double(words[1], mat[0][1]) &&
00151       string_to_double(words[2], mat[0][2]) &&
00152       string_to_double(words[3], mat[0][3]) &&
00153       string_to_double(words[4], mat[1][0]) &&
00154       string_to_double(words[5], mat[1][1]) &&
00155       string_to_double(words[6], mat[1][2]) &&
00156       string_to_double(words[7], mat[1][3]) &&
00157       string_to_double(words[8], mat[2][0]) &&
00158       string_to_double(words[9], mat[2][1]) &&
00159       string_to_double(words[10], mat[2][2]) &&
00160       string_to_double(words[11], mat[2][3]) &&
00161       string_to_double(words[12], mat[3][0]) &&
00162       string_to_double(words[13], mat[3][1]) &&
00163       string_to_double(words[14], mat[3][2]) &&
00164       string_to_double(words[15], mat[3][3]);
00165   }
00166 
00167   if (!okflag) {
00168     nout << "-" << opt
00169          << " requires sixteen numbers separated by commas.\n";
00170     return false;
00171   }
00172 
00173   orig *= mat;
00174 
00175   return true;
00176 }
00177 
00178 ////////////////////////////////////////////////////////////////////
00179 //     Function: ImageTransformColors::dispatch_mat3
00180 //       Access: Protected, Static
00181 //  Description: Takes a series of 9 numbers as a 3x3 matrix.
00182 ////////////////////////////////////////////////////////////////////
00183 bool ImageTransformColors::
00184 dispatch_mat3(const string &opt, const string &arg, void *var) {
00185   LMatrix4d &orig = *(LMatrix4d *)var;
00186 
00187   vector_string words;
00188   tokenize(arg, words, ",");
00189 
00190   LMatrix3d mat;
00191   bool okflag = false;
00192   if (words.size() == 9) {
00193     okflag =
00194       string_to_double(words[0], mat[0][0]) &&
00195       string_to_double(words[1], mat[0][1]) &&
00196       string_to_double(words[2], mat[0][2]) &&
00197       string_to_double(words[3], mat[1][0]) &&
00198       string_to_double(words[4], mat[1][1]) &&
00199       string_to_double(words[5], mat[1][2]) &&
00200       string_to_double(words[6], mat[2][0]) &&
00201       string_to_double(words[7], mat[2][1]) &&
00202       string_to_double(words[8], mat[2][2]);
00203   }
00204 
00205   if (!okflag) {
00206     nout << "-" << opt
00207          << " requires nine numbers separated by commas.\n";
00208     return false;
00209   }
00210 
00211   orig *= LMatrix4d(mat);
00212 
00213   return true;
00214 }
00215 
00216 ////////////////////////////////////////////////////////////////////
00217 //     Function: ImageTransformColors::dispatch_range
00218 //       Access: Protected, Static
00219 //  Description: Takes a min,max dynamic range.
00220 ////////////////////////////////////////////////////////////////////
00221 bool ImageTransformColors::
00222 dispatch_range(const string &opt, const string &arg, void *var) {
00223   LMatrix4d &orig = *(LMatrix4d *)var;
00224 
00225   vector_string words;
00226   tokenize(arg, words, ",");
00227 
00228   double min, max;
00229   bool okflag = false;
00230   if (words.size() == 2) {
00231     okflag =
00232       string_to_double(words[0], min) &&
00233       string_to_double(words[1], max);
00234   }
00235 
00236   if (!okflag) {
00237     nout << "-" << opt
00238          << " requires two numbers separated by commas.\n";
00239     return false;
00240   }
00241 
00242   orig *= LMatrix4d::scale_mat(max - min) * LMatrix4d::translate_mat(min);
00243 
00244   return true;
00245 }
00246 
00247 ////////////////////////////////////////////////////////////////////
00248 //     Function: ImageTransformColors::dispatch_scale
00249 //       Access: Protected, Static
00250 //  Description: Accepts a componentwise scale.
00251 ////////////////////////////////////////////////////////////////////
00252 bool ImageTransformColors::
00253 dispatch_scale(const string &opt, const string &arg, void *var) {
00254   LMatrix4d &orig = *(LMatrix4d *)var;
00255 
00256   vector_string words;
00257   tokenize(arg, words, ",");
00258 
00259   double r, g, b;
00260   bool okflag = false;
00261   if (words.size() == 3) {
00262     okflag =
00263       string_to_double(words[0], r) &&
00264       string_to_double(words[1], g) &&
00265       string_to_double(words[2], b);
00266   }
00267 
00268   if (!okflag) {
00269     nout << "-" << opt
00270          << " requires three numbers separated by commas.\n";
00271     return false;
00272   }
00273 
00274   orig *= LMatrix4d::scale_mat(r, g, b);
00275 
00276   return true;
00277 }
00278 
00279 ////////////////////////////////////////////////////////////////////
00280 //     Function: ImageTransformColors::dispatch_add
00281 //       Access: Protected, Static
00282 //  Description: Accepts a componentwise add.
00283 ////////////////////////////////////////////////////////////////////
00284 bool ImageTransformColors::
00285 dispatch_add(const string &opt, const string &arg, void *var) {
00286   LMatrix4d &orig = *(LMatrix4d *)var;
00287 
00288   vector_string words;
00289   tokenize(arg, words, ",");
00290 
00291   double r, g, b;
00292   bool okflag = false;
00293   if (words.size() == 3) {
00294     okflag =
00295       string_to_double(words[0], r) &&
00296       string_to_double(words[1], g) &&
00297       string_to_double(words[2], b);
00298   }
00299 
00300   if (!okflag) {
00301     nout << "-" << opt
00302          << " requires three numbers separated by commas.\n";
00303     return false;
00304   }
00305 
00306   orig *= LMatrix4d::translate_mat(r, g, b);
00307 
00308   return true;
00309 }
00310 
00311 ////////////////////////////////////////////////////////////////////
00312 //     Function: ImageTransformColors::handle_args
00313 //       Access: Protected, Virtual
00314 //  Description: Does something with the additional arguments on the
00315 //               command line (after all the -options have been
00316 //               parsed).  Returns true if the arguments are good,
00317 //               false otherwise.
00318 ////////////////////////////////////////////////////////////////////
00319 bool ImageTransformColors::
00320 handle_args(ProgramBase::Args &args) {
00321   if (args.empty()) {
00322     nout << "You must specify the image file(s) to read on the command line.\n";
00323     return false;
00324 
00325   } else {
00326     // These only apply if we have specified any image files.
00327     if (_got_output_filename && args.size() == 1) {
00328       if (_got_output_dirname) {
00329         nout << "Cannot specify both -o and -d.\n";
00330         return false;
00331       } else if (_inplace) {
00332         nout << "Cannot specify both -o and -inplace.\n";
00333         return false;
00334       }
00335 
00336     } else {
00337       if (_got_output_filename) {
00338         nout << "Cannot use -o when multiple image files are specified.\n";
00339         return false;
00340       }
00341 
00342       if (_got_output_dirname && _inplace) {
00343         nout << "Cannot specify both -inplace and -d.\n";
00344         return false;
00345 
00346       } else if (!_got_output_dirname && !_inplace) {
00347         nout << "You must specify either -inplace or -d.\n";
00348         return false;
00349       }
00350     }
00351   }
00352 
00353   Args::const_iterator ai;
00354   for (ai = args.begin(); ai != args.end(); ++ai) {
00355     Filename filename = (*ai);
00356     if (!filename.exists()) {
00357       nout << "Image file not found: " << filename << "\n";
00358       return false;
00359     }
00360     _filenames.push_back(filename);
00361   }
00362 
00363   return true;
00364 }
00365 
00366 ////////////////////////////////////////////////////////////////////
00367 //     Function: ImageTransformColors::get_output_filename
00368 //       Access: Protected
00369 //  Description: Returns the output filename of the egg file with the
00370 //               given input filename.  This is based on the user's
00371 //               choice of -inplace, -o, or -d.
00372 ////////////////////////////////////////////////////////////////////
00373 Filename ImageTransformColors::
00374 get_output_filename(const Filename &source_filename) const {
00375   if (_got_output_filename) {
00376     nassertr(!_inplace && !_got_output_dirname && _filenames.size() == 1, Filename());
00377     return _output_filename;
00378 
00379   } else if (_got_output_dirname) {
00380     nassertr(!_inplace, Filename());
00381     Filename result = source_filename;
00382     result.set_dirname(_output_dirname);
00383     return result;
00384   }
00385 
00386   nassertr(_inplace, Filename());
00387   return source_filename;
00388 }
00389 
00390 inline double
00391 hue2rgb(double m1, double m2, double h) {
00392   h -= floor(h);
00393   if (h < 1.0/6.0) {
00394     return m1 + (m2 - m1) * h * 6.0;
00395   }
00396   if (h < 1.0/2.0) {
00397     return m2;
00398   }
00399   if (h < 2.0/3.0) {
00400     return m1 + (m2 - m1) * (2.0/3.0 - h) * 6;
00401   }
00402   return m1;
00403 }
00404 
00405 static LRGBColord
00406 hls2rgb(const LRGBColord &hls) {
00407   double h = hls[0];
00408   double l = max(min(hls[1], 1.0), 0.0);
00409   double s = max(min(hls[2], 1.0), 0.0);
00410 
00411   double m2;
00412   if (l <= 0.5) {
00413     m2 = l * (s + 1.0);
00414   } else {
00415     m2 = l + s - l * s;
00416   }
00417   double m1 = l * 2 - m2;
00418 
00419   LRGBColord rgb(hue2rgb(m1, m2, h + 1.0/3.0),
00420                 hue2rgb(m1, m2, h),
00421                 hue2rgb(m1, m2, h - 1.0/3.0));
00422   return rgb;
00423 }
00424 
00425 static LRGBColord
00426 rgb2hls(const LRGBColord &rgb) {
00427   double r = rgb[0];
00428   double g = rgb[1];
00429   double b = rgb[2];
00430   double h, l, s;
00431 
00432   double minval = min(min(r, g), b);
00433   double maxval = max(max(r, g), b);
00434 
00435   double rnorm = 0.0, gnorm = 0.0, bnorm = 0.0;
00436   double mdiff = maxval - minval;
00437   double msum  = maxval + minval;
00438   l = 0.5 * msum;
00439   if (maxval == minval) {
00440     // Grayscale.
00441     return LRGBColord(0.0, l, 0.0);
00442   }
00443 
00444   rnorm = (maxval - r) / mdiff;
00445   gnorm = (maxval - g) / mdiff;
00446   bnorm = (maxval - b) / mdiff;
00447 
00448   if (l < 0.5) {
00449     s = mdiff / msum;
00450   } else {
00451     s = mdiff / (2.0 - msum);
00452   }
00453 
00454   if (r == maxval) {
00455     h = (6.0 + bnorm - gnorm) / 6.0;
00456   } else if (g == maxval) {
00457     h = (2.0 + rnorm - bnorm) / 6.0;
00458   } else {
00459     h = (4.0 + gnorm - rnorm) / 6.0;
00460   }
00461 
00462   if (h > 1.0) {
00463     h -= 1.0;
00464   }
00465 
00466   return LRGBColord(h, l, s);
00467 }
00468 
00469 ////////////////////////////////////////////////////////////////////
00470 //     Function: ImageTransformColors::process_image
00471 //       Access: Protected
00472 //  Description: Processes a single image in-place.
00473 ////////////////////////////////////////////////////////////////////
00474 void ImageTransformColors::
00475 process_image(PNMImage &image) {
00476   if (_hls) {
00477     for (int yi = 0; yi < image.get_y_size(); ++yi) {
00478       for (int xi = 0; xi < image.get_x_size(); ++xi) {
00479         LRGBColord rgb = image.get_xel(xi, yi);
00480         rgb = hls2rgb(_mat.xform_point(rgb2hls(rgb)));
00481         image.set_xel(xi, yi, rgb);
00482       }
00483     }    
00484   } else {  
00485     for (int yi = 0; yi < image.get_y_size(); ++yi) {
00486       for (int xi = 0; xi < image.get_x_size(); ++xi) {
00487         LRGBColord rgb = image.get_xel(xi, yi);
00488         rgb = _mat.xform_point(rgb);
00489         image.set_xel(xi, yi, rgb);
00490       }
00491     }    
00492   }
00493 }
00494 
00495 int main(int argc, char *argv[]) {
00496   // A call to pystub() to force libpystub.so to be linked in.
00497   pystub();
00498 
00499   ImageTransformColors prog;
00500   prog.parse_command_line(argc, argv);
00501   prog.run();
00502   return 0;
00503 }
 All Classes Functions Variables Enumerations