Panda3D
|
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 }