Panda3D
 All Classes Functions Variables Enumerations
imageTransformColors.cxx
1 // Filename: imageTransformColors.cxx
2 // Created by: drose (25Mar09)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "imageTransformColors.h"
16 #include "string_utils.h"
17 #include "pystub.h"
18 #include "pnmImage.h"
19 #include <math.h>
20 
21 ////////////////////////////////////////////////////////////////////
22 // Function: ImageTransformColors::Constructor
23 // Access: Public
24 // Description:
25 ////////////////////////////////////////////////////////////////////
26 ImageTransformColors::
27 ImageTransformColors() {
28  set_program_brief("transform colors in an image file");
29  set_program_description
30  ("This program can apply a global color transform to all of the "
31  "pixels in an image, or in a series of images. This can be used, "
32  "for instance, to increase or decrease the dynamic range; or to "
33  "rotate the hue; or to reduce the saturation of colors in the image.\n\n"
34 
35  "Each parameter is encoded in a 4x4 matrix, which modifies the R, G, B "
36  "colors of the image (the alpha values, if any, are not affected). "
37  "RGB values are clamped at 0 and 1 after the operation. "
38  "Multiple parameters are composed together in the order in which they "
39  "are listed.");
40 
41  add_option
42  ("hls", "", 0,
43  "Specifies that all of the matrix operations are performed in HLS "
44  "space, instead of the default RGB space. In this mode, the first "
45  "component controls hue, the second controls lightness, and the third "
46  "controls saturation.",
47  &ImageTransformColors::dispatch_none, &_hls, NULL);
48 
49  add_option
50  ("range", "min,max", 0,
51  "Compresses the overall dynamic range from 0,1 to min,max. If min,max "
52  "exceed 0,1, the dynamic range is expanded. This doesn't make sense in "
53  "HLS mode.",
54  &ImageTransformColors::dispatch_range, NULL, &_mat);
55 
56  add_option
57  ("scale", "r,g,b", 0,
58  "Scales the r,g,b components by the indicated values. In HLS mode, "
59  "the scale is applied to the h,l,s components.",
60  &ImageTransformColors::dispatch_scale, NULL, &_mat);
61 
62  add_option
63  ("add", "r,g,b", 0,
64  "Adds the indicated values to the r,g,b components. In HLS mode, "
65  "the sum is applied to the h,l,s components.",
66  &ImageTransformColors::dispatch_add, NULL, &_mat);
67 
68  add_option
69  ("mat4", "m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33",
70  0, "Defines an arbitrary 4x4 RGB matrix.",
71  &ImageTransformColors::dispatch_mat4, NULL, &_mat);
72 
73  add_option
74  ("mat3", "m00,m01,m02,m10,m11,m12,m20,m21,m22", 0,
75  "Defines an arbitrary 3x3 RGB matrix.",
76  &ImageTransformColors::dispatch_mat3, NULL, &_mat);
77 
78  add_option
79  ("o", "filename", 50,
80  "Specify the filename to which the resulting image file will be written. "
81  "This is only valid when there is only one input image file on the command "
82  "line. If you want to process multiple files simultaneously, you must "
83  "use either -d or -inplace.",
84  &ImageTransformColors::dispatch_filename, &_got_output_filename, &_output_filename);
85 
86  add_option
87  ("d", "dirname", 50,
88  "Specify the name of the directory in which to write the resulting image "
89  "files. If you are processing only one image file, this may be omitted "
90  "in lieu of the -o option. If you are processing multiple image files, "
91  "this may be omitted only if you specify -inplace instead.",
92  &ImageTransformColors::dispatch_filename, &_got_output_dirname, &_output_dirname);
93 
94  add_option
95  ("inplace", "", 50,
96  "If this option is given, the input image files will be rewritten in "
97  "place with the results. This obviates the need to specify -d "
98  "for an output directory; however, it's risky because the original "
99  "input image files are lost.",
100  &ImageTransformColors::dispatch_none, &_inplace);
101 
102  _mat = LMatrix4d::ident_mat();
103 }
104 
105 ////////////////////////////////////////////////////////////////////
106 // Function: ImageTransformColors::run
107 // Access: Public
108 // Description:
109 ////////////////////////////////////////////////////////////////////
110 void ImageTransformColors::
111 run() {
112  _mat.write(nout, 0);
113  nout << "\n";
114 
115  Filenames::iterator fi;
116  for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) {
117  const Filename &source_filename = (*fi);
118  nout << source_filename << "\n";
119  PNMImage image;
120  if (!image.read(source_filename)) {
121  nout << "Couldn't read " << source_filename << "; ignoring.\n";
122  continue;
123  }
124 
125  process_image(image);
126 
127  Filename output_filename = get_output_filename(source_filename);
128  if (!image.write(output_filename)) {
129  nout << "Couldn't write " << output_filename << "; ignoring.\n";
130  }
131  }
132 }
133 
134 ////////////////////////////////////////////////////////////////////
135 // Function: ImageTransformColors::dispatch_mat4
136 // Access: Protected, Static
137 // Description: Takes a series of 16 numbers as a 4x4 matrix.
138 ////////////////////////////////////////////////////////////////////
139 bool ImageTransformColors::
140 dispatch_mat4(const string &opt, const string &arg, void *var) {
141  LMatrix4d &orig = *(LMatrix4d *)var;
142 
143  vector_string words;
144  tokenize(arg, words, ",");
145 
146  LMatrix4d mat;
147  bool okflag = false;
148  if (words.size() == 16) {
149  okflag =
150  string_to_double(words[0], mat[0][0]) &&
151  string_to_double(words[1], mat[0][1]) &&
152  string_to_double(words[2], mat[0][2]) &&
153  string_to_double(words[3], mat[0][3]) &&
154  string_to_double(words[4], mat[1][0]) &&
155  string_to_double(words[5], mat[1][1]) &&
156  string_to_double(words[6], mat[1][2]) &&
157  string_to_double(words[7], mat[1][3]) &&
158  string_to_double(words[8], mat[2][0]) &&
159  string_to_double(words[9], mat[2][1]) &&
160  string_to_double(words[10], mat[2][2]) &&
161  string_to_double(words[11], mat[2][3]) &&
162  string_to_double(words[12], mat[3][0]) &&
163  string_to_double(words[13], mat[3][1]) &&
164  string_to_double(words[14], mat[3][2]) &&
165  string_to_double(words[15], mat[3][3]);
166  }
167 
168  if (!okflag) {
169  nout << "-" << opt
170  << " requires sixteen numbers separated by commas.\n";
171  return false;
172  }
173 
174  orig *= mat;
175 
176  return true;
177 }
178 
179 ////////////////////////////////////////////////////////////////////
180 // Function: ImageTransformColors::dispatch_mat3
181 // Access: Protected, Static
182 // Description: Takes a series of 9 numbers as a 3x3 matrix.
183 ////////////////////////////////////////////////////////////////////
184 bool ImageTransformColors::
185 dispatch_mat3(const string &opt, const string &arg, void *var) {
186  LMatrix4d &orig = *(LMatrix4d *)var;
187 
188  vector_string words;
189  tokenize(arg, words, ",");
190 
191  LMatrix3d mat;
192  bool okflag = false;
193  if (words.size() == 9) {
194  okflag =
195  string_to_double(words[0], mat[0][0]) &&
196  string_to_double(words[1], mat[0][1]) &&
197  string_to_double(words[2], mat[0][2]) &&
198  string_to_double(words[3], mat[1][0]) &&
199  string_to_double(words[4], mat[1][1]) &&
200  string_to_double(words[5], mat[1][2]) &&
201  string_to_double(words[6], mat[2][0]) &&
202  string_to_double(words[7], mat[2][1]) &&
203  string_to_double(words[8], mat[2][2]);
204  }
205 
206  if (!okflag) {
207  nout << "-" << opt
208  << " requires nine numbers separated by commas.\n";
209  return false;
210  }
211 
212  orig *= LMatrix4d(mat);
213 
214  return true;
215 }
216 
217 ////////////////////////////////////////////////////////////////////
218 // Function: ImageTransformColors::dispatch_range
219 // Access: Protected, Static
220 // Description: Takes a min,max dynamic range.
221 ////////////////////////////////////////////////////////////////////
222 bool ImageTransformColors::
223 dispatch_range(const string &opt, const string &arg, void *var) {
224  LMatrix4d &orig = *(LMatrix4d *)var;
225 
226  vector_string words;
227  tokenize(arg, words, ",");
228 
229  double min, max;
230  bool okflag = false;
231  if (words.size() == 2) {
232  okflag =
233  string_to_double(words[0], min) &&
234  string_to_double(words[1], max);
235  }
236 
237  if (!okflag) {
238  nout << "-" << opt
239  << " requires two numbers separated by commas.\n";
240  return false;
241  }
242 
243  orig *= LMatrix4d::scale_mat(max - min) * LMatrix4d::translate_mat(min);
244 
245  return true;
246 }
247 
248 ////////////////////////////////////////////////////////////////////
249 // Function: ImageTransformColors::dispatch_scale
250 // Access: Protected, Static
251 // Description: Accepts a componentwise scale.
252 ////////////////////////////////////////////////////////////////////
253 bool ImageTransformColors::
254 dispatch_scale(const string &opt, const string &arg, void *var) {
255  LMatrix4d &orig = *(LMatrix4d *)var;
256 
257  vector_string words;
258  tokenize(arg, words, ",");
259 
260  double r, g, b;
261  bool okflag = false;
262  if (words.size() == 3) {
263  okflag =
264  string_to_double(words[0], r) &&
265  string_to_double(words[1], g) &&
266  string_to_double(words[2], b);
267  }
268 
269  if (!okflag) {
270  nout << "-" << opt
271  << " requires three numbers separated by commas.\n";
272  return false;
273  }
274 
275  orig *= LMatrix4d::scale_mat(r, g, b);
276 
277  return true;
278 }
279 
280 ////////////////////////////////////////////////////////////////////
281 // Function: ImageTransformColors::dispatch_add
282 // Access: Protected, Static
283 // Description: Accepts a componentwise add.
284 ////////////////////////////////////////////////////////////////////
285 bool ImageTransformColors::
286 dispatch_add(const string &opt, const string &arg, void *var) {
287  LMatrix4d &orig = *(LMatrix4d *)var;
288 
289  vector_string words;
290  tokenize(arg, words, ",");
291 
292  double r, g, b;
293  bool okflag = false;
294  if (words.size() == 3) {
295  okflag =
296  string_to_double(words[0], r) &&
297  string_to_double(words[1], g) &&
298  string_to_double(words[2], b);
299  }
300 
301  if (!okflag) {
302  nout << "-" << opt
303  << " requires three numbers separated by commas.\n";
304  return false;
305  }
306 
307  orig *= LMatrix4d::translate_mat(r, g, b);
308 
309  return true;
310 }
311 
312 ////////////////////////////////////////////////////////////////////
313 // Function: ImageTransformColors::handle_args
314 // Access: Protected, Virtual
315 // Description: Does something with the additional arguments on the
316 // command line (after all the -options have been
317 // parsed). Returns true if the arguments are good,
318 // false otherwise.
319 ////////////////////////////////////////////////////////////////////
320 bool ImageTransformColors::
321 handle_args(ProgramBase::Args &args) {
322  if (args.empty()) {
323  nout << "You must specify the image file(s) to read on the command line.\n";
324  return false;
325 
326  } else {
327  // These only apply if we have specified any image files.
328  if (_got_output_filename && args.size() == 1) {
329  if (_got_output_dirname) {
330  nout << "Cannot specify both -o and -d.\n";
331  return false;
332  } else if (_inplace) {
333  nout << "Cannot specify both -o and -inplace.\n";
334  return false;
335  }
336 
337  } else {
338  if (_got_output_filename) {
339  nout << "Cannot use -o when multiple image files are specified.\n";
340  return false;
341  }
342 
343  if (_got_output_dirname && _inplace) {
344  nout << "Cannot specify both -inplace and -d.\n";
345  return false;
346 
347  } else if (!_got_output_dirname && !_inplace) {
348  nout << "You must specify either -inplace or -d.\n";
349  return false;
350  }
351  }
352  }
353 
354  Args::const_iterator ai;
355  for (ai = args.begin(); ai != args.end(); ++ai) {
356  Filename filename = (*ai);
357  if (!filename.exists()) {
358  nout << "Image file not found: " << filename << "\n";
359  return false;
360  }
361  _filenames.push_back(filename);
362  }
363 
364  return true;
365 }
366 
367 ////////////////////////////////////////////////////////////////////
368 // Function: ImageTransformColors::get_output_filename
369 // Access: Protected
370 // Description: Returns the output filename of the egg file with the
371 // given input filename. This is based on the user's
372 // choice of -inplace, -o, or -d.
373 ////////////////////////////////////////////////////////////////////
374 Filename ImageTransformColors::
375 get_output_filename(const Filename &source_filename) const {
376  if (_got_output_filename) {
377  nassertr(!_inplace && !_got_output_dirname && _filenames.size() == 1, Filename());
378  return _output_filename;
379 
380  } else if (_got_output_dirname) {
381  nassertr(!_inplace, Filename());
382  Filename result = source_filename;
383  result.set_dirname(_output_dirname);
384  return result;
385  }
386 
387  nassertr(_inplace, Filename());
388  return source_filename;
389 }
390 
391 inline double
392 hue2rgb(double m1, double m2, double h) {
393  h -= floor(h);
394  if (h < 1.0/6.0) {
395  return m1 + (m2 - m1) * h * 6.0;
396  }
397  if (h < 1.0/2.0) {
398  return m2;
399  }
400  if (h < 2.0/3.0) {
401  return m1 + (m2 - m1) * (2.0/3.0 - h) * 6;
402  }
403  return m1;
404 }
405 
406 static LRGBColord
407 hls2rgb(const LRGBColord &hls) {
408  double h = hls[0];
409  double l = max(min(hls[1], 1.0), 0.0);
410  double s = max(min(hls[2], 1.0), 0.0);
411 
412  double m2;
413  if (l <= 0.5) {
414  m2 = l * (s + 1.0);
415  } else {
416  m2 = l + s - l * s;
417  }
418  double m1 = l * 2 - m2;
419 
420  LRGBColord rgb(hue2rgb(m1, m2, h + 1.0/3.0),
421  hue2rgb(m1, m2, h),
422  hue2rgb(m1, m2, h - 1.0/3.0));
423  return rgb;
424 }
425 
426 static LRGBColord
427 rgb2hls(const LRGBColord &rgb) {
428  double r = rgb[0];
429  double g = rgb[1];
430  double b = rgb[2];
431  double h, l, s;
432 
433  double minval = min(min(r, g), b);
434  double maxval = max(max(r, g), b);
435 
436  double rnorm = 0.0, gnorm = 0.0, bnorm = 0.0;
437  double mdiff = maxval - minval;
438  double msum = maxval + minval;
439  l = 0.5 * msum;
440  if (maxval == minval) {
441  // Grayscale.
442  return LRGBColord(0.0, l, 0.0);
443  }
444 
445  rnorm = (maxval - r) / mdiff;
446  gnorm = (maxval - g) / mdiff;
447  bnorm = (maxval - b) / mdiff;
448 
449  if (l < 0.5) {
450  s = mdiff / msum;
451  } else {
452  s = mdiff / (2.0 - msum);
453  }
454 
455  if (r == maxval) {
456  h = (6.0 + bnorm - gnorm) / 6.0;
457  } else if (g == maxval) {
458  h = (2.0 + rnorm - bnorm) / 6.0;
459  } else {
460  h = (4.0 + gnorm - rnorm) / 6.0;
461  }
462 
463  if (h > 1.0) {
464  h -= 1.0;
465  }
466 
467  return LRGBColord(h, l, s);
468 }
469 
470 ////////////////////////////////////////////////////////////////////
471 // Function: ImageTransformColors::process_image
472 // Access: Protected
473 // Description: Processes a single image in-place.
474 ////////////////////////////////////////////////////////////////////
475 void ImageTransformColors::
476 process_image(PNMImage &image) {
477  if (_hls) {
478  for (int yi = 0; yi < image.get_y_size(); ++yi) {
479  for (int xi = 0; xi < image.get_x_size(); ++xi) {
480  LRGBColord rgb = LCAST(double, image.get_xel(xi, yi));
481  rgb = hls2rgb(_mat.xform_point(rgb2hls(rgb)));
482  image.set_xel(xi, yi, LCAST(float, rgb));
483  }
484  }
485  } else {
486  for (int yi = 0; yi < image.get_y_size(); ++yi) {
487  for (int xi = 0; xi < image.get_x_size(); ++xi) {
488  LRGBColord rgb = LCAST(double, image.get_xel(xi, yi));
489  rgb = _mat.xform_point(rgb);
490  image.set_xel(xi, yi, LCAST(float, rgb));
491  }
492  }
493  }
494 }
495 
496 int main(int argc, char *argv[]) {
497  // A call to pystub() to force libpystub.so to be linked in.
498  pystub();
499 
501  prog.parse_command_line(argc, argv);
502  prog.run();
503  return 0;
504 }
bool write(const Filename &filename, PNMFileType *type=NULL) const
Writes the image to the indicated filename.
Definition: pnmImage.cxx:362
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:4716
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:68
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
bool read(const Filename &filename, PNMFileType *type=NULL, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:245
static LMatrix4d scale_mat(const LVecBase3d &scale)
Returns a matrix that applies the indicated scale in each of the three axes.
Definition: lmatrix.h:6721
static LMatrix4d translate_mat(const LVecBase3d &trans)
Returns a matrix that applies the indicated translation.
Definition: lmatrix.h:6662
int get_x_size() const
Returns the number of pixels in the X direction.
int get_y_size() const
Returns the number of pixels in the Y direction.
This is a 3-by-3 transform matrix.
Definition: lmatrix.h:4375
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
static const LMatrix4d & ident_mat()
Returns an identity matrix.
Definition: lmatrix.h:5168
void set_dirname(const string &s)
Replaces the directory part of the filename.
Definition: filename.cxx:726
This is the base class for all three-component vectors and points.
Definition: lvecBase3.h:1455
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
This program can apply a 4x4 color transform to all of the colors in the pixels of a series of images...
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition: pnmImage.I:597
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:641
LVecBase3d xform_point(const LVecBase3d &v) const
The matrix transforms a 3-component point (including translation component) and returns the result...
Definition: lmatrix.h:5932