00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
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
00023
00024
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
00106
00107
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
00135
00136
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
00180
00181
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
00218
00219
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
00249
00250
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
00281
00282
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
00313
00314
00315
00316
00317
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
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
00368
00369
00370
00371
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
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
00471
00472
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
00497 pystub();
00498
00499 ImageTransformColors prog;
00500 prog.parse_command_line(argc, argv);
00501 prog.run();
00502 return 0;
00503 }