Panda3D
txaLine.cxx
1 // Filename: txaLine.cxx
2 // Created by: drose (30Nov00)
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 "txaLine.h"
16 #include "pal_string_utils.h"
17 #include "eggFile.h"
18 #include "palettizer.h"
19 #include "textureImage.h"
20 #include "sourceTextureImage.h"
21 #include "paletteGroup.h"
22 
23 #include "pnotify.h"
24 #include "pnmFileType.h"
25 
26 ////////////////////////////////////////////////////////////////////
27 // Function: TxaLine::Constructor
28 // Access: Public
29 // Description:
30 ////////////////////////////////////////////////////////////////////
31 TxaLine::
32 TxaLine() {
33  _size_type = ST_none;
34  _scale = 0.0;
35  _x_size = 0;
36  _y_size = 0;
37  _aniso_degree = 0;
38  _num_channels = 0;
39  _format = EggTexture::F_unspecified;
40  _force_format = false;
41  _generic_format = false;
42  _keep_format = false;
43  _alpha_mode = EggRenderMode::AM_unspecified;
44  _wrap_u = EggTexture::WM_unspecified;
45  _wrap_v = EggTexture::WM_unspecified;
46  _quality_level = EggTexture::QL_unspecified;
47  _got_margin = false;
48  _margin = 0;
49  _got_coverage_threshold = false;
50  _coverage_threshold = 0.0;
51  _color_type = (PNMFileType *)NULL;
52  _alpha_type = (PNMFileType *)NULL;
53 }
54 
55 ////////////////////////////////////////////////////////////////////
56 // Function: TxaLine::parse
57 // Access: Public
58 // Description: Accepts a string that defines a line of the .txa file
59 // and parses it into its constinuent parts. Returns
60 // true if successful, false on error.
61 ////////////////////////////////////////////////////////////////////
62 bool TxaLine::
63 parse(const string &line) {
64  size_t colon = line.find(':');
65  if (colon == string::npos) {
66  nout << "Colon required.\n";
67  return false;
68  }
69 
70  // Chop up the first part of the string (preceding the colon) into
71  // its individual words. These are patterns to match.
72  vector_string words;
73  extract_words(line.substr(0, colon), words);
74 
75  vector_string::iterator wi;
76  for (wi = words.begin(); wi != words.end(); ++wi) {
77  string word = (*wi);
78 
79  // If the pattern ends in the string ".egg", and only if it ends
80  // in this string, it is deemed an egg pattern and will only be
81  // tested against egg files. If it ends in anything else, it is
82  // deemed a texture pattern and will only be tested against
83  // textures.
84  if (word.length() > 4 && word.substr(word.length() - 4) == ".egg") {
85  GlobPattern pattern(word);
86  pattern.set_case_sensitive(false);
87  _egg_patterns.push_back(pattern);
88 
89  } else {
90  // However, the filename extension, if any, is stripped off
91  // because the texture key names nowadays don't include them.
92  size_t dot = word.rfind('.');
93  if (dot != string::npos) {
94  word = word.substr(0, dot);
95  }
96  GlobPattern pattern(word);
97  pattern.set_case_sensitive(false);
98  _texture_patterns.push_back(pattern);
99  }
100  }
101 
102  if (_egg_patterns.empty() && _texture_patterns.empty()) {
103  nout << "No texture or egg filenames given.\n";
104  return false;
105  }
106 
107  // Now chop up the rest of the string (following the colon) into its
108  // individual words. These are keywords and size indications.
109  words.clear();
110  extract_words(line.substr(colon + 1), words);
111 
112  wi = words.begin();
113  while (wi != words.end()) {
114  const string &word = *wi;
115  nassertr(!word.empty(), false);
116 
117  if (isdigit(word[0])) {
118  // This is either a new size or a scale percentage.
119  if (_size_type != ST_none) {
120  nout << "Invalid repeated size request: " << word << "\n";
121  return false;
122  }
123  if (word[word.length() - 1] == '%') {
124  // It's a scale percentage!
125  _size_type = ST_scale;
126 
127  string tail;
128  _scale = string_to_double(word, tail);
129  if (!(tail == "%")) {
130  // This is an invalid number.
131  return false;
132  }
133  ++wi;
134 
135  } else {
136  // Collect a number of consecutive numeric fields.
137  pvector<int> numbers;
138  while (wi != words.end() && isdigit((*wi)[0])) {
139  const string &word = *wi;
140  int num;
141  if (!string_to_int(word, num)) {
142  nout << "Invalid size: " << word << "\n";
143  return false;
144  }
145  numbers.push_back(num);
146  ++wi;
147  }
148  if (numbers.size() < 2) {
149  nout << "At least two size numbers must be given, or a percent sign used to indicate scaling.\n";
150  return false;
151 
152  } else if (numbers.size() == 2) {
153  _size_type = ST_explicit_2;
154  _x_size = numbers[0];
155  _y_size = numbers[1];
156 
157  } else if (numbers.size() == 3) {
158  _size_type = ST_explicit_3;
159  _x_size = numbers[0];
160  _y_size = numbers[1];
161  _num_channels = numbers[2];
162 
163  } else {
164  nout << "Too many size numbers given.\n";
165  return false;
166  }
167  }
168 
169  } else {
170  // The word does not begin with a digit; therefore it's either a
171  // keyword or an image file type request.
172  if (word == "omit") {
173  _keywords.push_back(KW_omit);
174 
175  } else if (word == "nearest") {
176  _keywords.push_back(KW_nearest);
177 
178  } else if (word == "linear") {
179  _keywords.push_back(KW_linear);
180 
181  } else if (word == "mipmap") {
182  _keywords.push_back(KW_mipmap);
183 
184  } else if (word == "cont") {
185  _keywords.push_back(KW_cont);
186 
187  } else if (word == "margin") {
188  ++wi;
189  if (wi == words.end()) {
190  nout << "Argument required for 'margin'.\n";
191  return false;
192  }
193 
194  const string &arg = (*wi);
195  if (!string_to_int(arg, _margin)) {
196  nout << "Not an integer: " << arg << "\n";
197  return false;
198  }
199  if (_margin < 0) {
200  nout << "Invalid margin: " << _margin << "\n";
201  return false;
202  }
203  _got_margin = true;
204 
205  } else if (word == "aniso") {
206  ++wi;
207  if (wi == words.end()) {
208  nout << "Integer argument required for 'aniso'.\n";
209  return false;
210  }
211 
212  const string &arg = (*wi);
213  if (!string_to_int(arg, _aniso_degree)) {
214  nout << "Not an integer: " << arg << "\n";
215  return false;
216  }
217  if ((_aniso_degree < 2) || (_aniso_degree > 16)) {
218  // make it an error to specific degree 0 or 1, which means no anisotropy so it's probably an input mistake
219  nout << "Invalid anistropic degree (range is 2-16): " << _aniso_degree << "\n";
220  return false;
221  }
222 
223  _keywords.push_back(KW_anisotropic);
224 
225  } else if (word == "coverage") {
226  ++wi;
227  if (wi == words.end()) {
228  nout << "Argument required for 'coverage'.\n";
229  return false;
230  }
231 
232  const string &arg = (*wi);
233  if (!string_to_double(arg, _coverage_threshold)) {
234  nout << "Not a number: " << arg << "\n";
235  return false;
236  }
237  if (_coverage_threshold <= 0.0) {
238  nout << "Invalid coverage threshold: " << _coverage_threshold << "\n";
239  return false;
240  }
241  _got_coverage_threshold = true;
242 
243  } else if (word.substr(0, 6) == "force-") {
244  // Force a particular format, despite the number of channels
245  // in the image.
246  string format_name = word.substr(6);
247  EggTexture::Format format = EggTexture::string_format(format_name);
248  if (format != EggTexture::F_unspecified) {
249  _format = format;
250  _force_format = true;
251  } else {
252  nout << "Unknown image format: " << format_name << "\n";
253  return false;
254  }
255 
256  } else if (word == "generic") {
257  // Genericize the image format by replacing bitcount-specific
258  // formats with their generic equivalents, e.g. rgba8 becomes
259  // rgba.
260  _generic_format = true;
261 
262  } else if (word == "keep-format") {
263  // Keep whatever image format was specified.
264  _keep_format = true;
265 
266  } else {
267  // Maybe it's a group name.
268  PaletteGroup *group = pal->test_palette_group(word);
269  if (group != (PaletteGroup *)NULL) {
270  _palette_groups.insert(group);
271 
272  } else {
273  // Maybe it's a format name. This suggests an image format,
274  // but may be overridden to reflect the number of channels in
275  // the image.
276  EggTexture::Format format = EggTexture::string_format(word);
277  if (format != EggTexture::F_unspecified) {
278  if (!_force_format) {
279  _format = format;
280  }
281  } else {
282  // Maybe it's an alpha mode.
283  EggRenderMode::AlphaMode am = EggRenderMode::string_alpha_mode(word);
284  if (am != EggRenderMode::AM_unspecified) {
285  _alpha_mode = am;
286 
287  } else {
288  // Maybe it's a quality level.
289  EggTexture::QualityLevel ql = EggTexture::string_quality_level(word);
290  if (ql != EggTexture::QL_unspecified) {
291  _quality_level = ql;
292 
293  } else if (word.length() > 2 && word[word.length() - 2] == '_' &&
294  strchr("uv", word[word.length() - 1]) != NULL) {
295  // It must be a wrap mode for u or v.
296  string prefix = word.substr(0, word.length() - 2);
297  EggTexture::WrapMode wm = EggTexture::string_wrap_mode(prefix);
298  if (wm == EggTexture::WM_unspecified) {
299  return false;
300  }
301  switch (word[word.length() - 1]) {
302  case 'u':
303  _wrap_u = wm;
304  break;
305 
306  case 'v':
307  _wrap_v = wm;
308  break;
309  }
310 
311  } else {
312  // Maybe it's an image file request.
313  if (!parse_image_type_request(word, _color_type, _alpha_type)) {
314  return false;
315  }
316  }
317  }
318  }
319  }
320  }
321  ++wi;
322  }
323  }
324 
325  return true;
326 }
327 
328 ////////////////////////////////////////////////////////////////////
329 // Function: TxaLine::match_egg
330 // Access: Public
331 // Description: Compares the patterns on the line to the indicated
332 // EggFile. If they match, updates the egg with the
333 // appropriate information. Returns true if a match is
334 // detected and the search for another line should stop,
335 // or false if a match is not detected (or if the
336 // keyword "cont" is present, which means the search
337 // should continue regardless).
338 ////////////////////////////////////////////////////////////////////
339 bool TxaLine::
340 match_egg(EggFile *egg_file) const {
341  string name = egg_file->get_name();
342 
343  bool matched_any = false;
344  Patterns::const_iterator pi;
345  for (pi = _egg_patterns.begin();
346  pi != _egg_patterns.end() && !matched_any;
347  ++pi) {
348  matched_any = (*pi).matches(name);
349  }
350 
351  if (!matched_any) {
352  // No match this line; continue.
353  return false;
354  }
355 
356  bool got_cont = false;
357  Keywords::const_iterator ki;
358  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
359  switch (*ki) {
360  case KW_omit:
361  break;
362 
363  case KW_nearest:
364  case KW_linear:
365  case KW_mipmap:
366  case KW_anisotropic:
367  // These mean nothing to an egg file.
368  break;
369 
370  case KW_cont:
371  got_cont = true;
372  break;
373  }
374  }
375 
376  egg_file->match_txa_groups(_palette_groups);
377 
378  if (got_cont) {
379  // If we have the "cont" keyword, we should keep scanning for
380  // another line, even though we matched this one.
381  return false;
382  }
383 
384  // Otherwise, in the normal case, a match ends the search for
385  // matches.
386  egg_file->clear_surprise();
387 
388  return true;
389 }
390 
391 ////////////////////////////////////////////////////////////////////
392 // Function: TxaLine::match_texture
393 // Access: Public
394 // Description: Compares the patterns on the line to the indicated
395 // TextureImage. If they match, updates the texture
396 // with the appropriate information. Returns true if a
397 // match is detected and the search for another line
398 // should stop, or false if a match is not detected (or
399 // if the keyword "cont" is present, which means the
400 // search should continue regardless).
401 ////////////////////////////////////////////////////////////////////
402 bool TxaLine::
403 match_texture(TextureImage *texture) const {
404  string name = texture->get_name();
405 
406  bool matched_any = false;
407  Patterns::const_iterator pi;
408  for (pi = _texture_patterns.begin();
409  pi != _texture_patterns.end() && !matched_any;
410  ++pi) {
411  matched_any = (*pi).matches(name);
412  }
413 
414  if (!matched_any) {
415  // No match this line; continue.
416  return false;
417  }
418 
419  SourceTextureImage *source = texture->get_preferred_source();
420  TextureRequest &request = texture->_request;
421 
422  if (!request._got_size) {
423  switch (_size_type) {
424  case ST_none:
425  break;
426 
427  case ST_scale:
428  if (source != (SourceTextureImage *)NULL && source->get_size()) {
429  request._got_size = true;
430  request._x_size = max(1, (int)(source->get_x_size() * _scale / 100.0));
431  request._y_size = max(1, (int)(source->get_y_size() * _scale / 100.0));
432  }
433  break;
434 
435  case ST_explicit_3:
436  request._got_num_channels = true;
437  request._num_channels = _num_channels;
438  // fall through
439 
440  case ST_explicit_2:
441  request._got_size = true;
442  request._x_size = _x_size;
443  request._y_size = _y_size;
444  break;
445  }
446  }
447 
448  if (_got_margin) {
449  request._margin = _margin;
450  }
451 
452  if (_got_coverage_threshold) {
453  request._coverage_threshold = _coverage_threshold;
454  }
455 
456  if (_color_type != (PNMFileType *)NULL) {
457  request._properties._color_type = _color_type;
458  request._properties._alpha_type = _alpha_type;
459  }
460 
461  if (_quality_level != EggTexture::QL_unspecified) {
462  request._properties._quality_level = _quality_level;
463  }
464 
465  if (_format != EggTexture::F_unspecified) {
466  request._format = _format;
467  request._force_format = _force_format;
468  request._generic_format = false;
469  }
470 
471  if (_generic_format) {
472  request._generic_format = true;
473  }
474 
475  if (_keep_format) {
476  request._keep_format = true;
477  }
478 
479  if (_alpha_mode != EggRenderMode::AM_unspecified) {
480  request._alpha_mode = _alpha_mode;
481  }
482 
483  if (_wrap_u != EggTexture::WM_unspecified) {
484  request._wrap_u = _wrap_u;
485  }
486  if (_wrap_v != EggTexture::WM_unspecified) {
487  request._wrap_v = _wrap_v;
488  }
489 
490  bool got_cont = false;
491  Keywords::const_iterator ki;
492  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
493  switch (*ki) {
494  case KW_omit:
495  request._omit = true;
496  break;
497 
498  case KW_nearest:
499  request._minfilter = EggTexture::FT_nearest;
500  request._magfilter = EggTexture::FT_nearest;
501  break;
502 
503  case KW_linear:
504  request._minfilter = EggTexture::FT_linear;
505  request._magfilter = EggTexture::FT_linear;
506  break;
507 
508  case KW_mipmap:
509  request._minfilter = EggTexture::FT_linear_mipmap_linear;
510  request._magfilter = EggTexture::FT_linear_mipmap_linear;
511  break;
512 
513  case KW_anisotropic:
514  request._anisotropic_degree = _aniso_degree;
515  break;
516 
517  case KW_cont:
518  got_cont = true;
519  break;
520  }
521  }
522 
523  texture->_explicitly_assigned_groups.make_union
524  (texture->_explicitly_assigned_groups, _palette_groups);
525  texture->_explicitly_assigned_groups.remove_null();
526 
527  if (got_cont) {
528  // If we have the "cont" keyword, we should keep scanning for
529  // another line, even though we matched this one.
530  return false;
531  }
532 
533  // Otherwise, in the normal case, a match ends the search for
534  // matches.
535  texture->_is_surprise = false;
536 
537  return true;
538 }
539 
540 ////////////////////////////////////////////////////////////////////
541 // Function: TxaLine::output
542 // Access: Public
543 // Description:
544 ////////////////////////////////////////////////////////////////////
545 void TxaLine::
546 output(ostream &out) const {
547  Patterns::const_iterator pi;
548  for (pi = _texture_patterns.begin(); pi != _texture_patterns.end(); ++pi) {
549  out << (*pi) << " ";
550  }
551  for (pi = _egg_patterns.begin(); pi != _egg_patterns.end(); ++pi) {
552  out << (*pi) << " ";
553  }
554  out << ":";
555 
556  switch (_size_type) {
557  case ST_none:
558  break;
559 
560  case ST_scale:
561  out << " " << _scale << "%";
562  break;
563 
564  case ST_explicit_2:
565  out << " " << _x_size << " " << _y_size;
566  break;
567 
568  case ST_explicit_3:
569  out << " " << _x_size << " " << _y_size << " " << _num_channels;
570  break;
571  }
572 
573  if (_got_margin) {
574  out << " margin " << _margin;
575  }
576 
577  if (_got_coverage_threshold) {
578  out << " coverage " << _coverage_threshold;
579  }
580 
581  Keywords::const_iterator ki;
582  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
583  switch (*ki) {
584  case KW_omit:
585  out << " omit";
586  break;
587 
588  case KW_nearest:
589  out << " nearest";
590  break;
591 
592  case KW_linear:
593  out << " linear";
594  break;
595 
596  case KW_mipmap:
597  out << " mipmap";
598  break;
599 
600  case KW_cont:
601  out << " cont";
602  break;
603 
604  case KW_anisotropic:
605  out << " aniso " << _aniso_degree;
606  break;
607  }
608  }
609 
610  PaletteGroups::const_iterator gi;
611  for (gi = _palette_groups.begin(); gi != _palette_groups.end(); ++gi) {
612  out << " " << (*gi)->get_name();
613  }
614 
615  if (_format != EggTexture::F_unspecified) {
616  out << " " << _format;
617  if (_force_format) {
618  out << " (forced)";
619  }
620  }
621 
622  if (_color_type != (PNMFileType *)NULL) {
623  out << " " << _color_type->get_suggested_extension();
624  if (_alpha_type != (PNMFileType *)NULL) {
625  out << "," << _alpha_type->get_suggested_extension();
626  }
627  }
628 }
iterator end() const
Returns an iterator suitable for traversing the set.
static WrapMode string_wrap_mode(const string &string)
Returns the WrapMode value associated with the given string representation, or WM_unspecified if the ...
Definition: eggTexture.cxx:814
static AlphaMode string_alpha_mode(const string &string)
Returns the AlphaMode value associated with the given string representation, or AM_unspecified if the...
void remove_null()
Removes the special "null" group from the set.
PaletteGroup * test_palette_group(const string &name) const
Returns the PaletteGroup with the given name.
Definition: palettizer.cxx:868
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:106
void clear_surprise()
Removes the &#39;surprise&#39; flag; this file has been successfully matched against a line in the ...
Definition: eggFile.cxx:320
iterator begin() const
Returns an iterator suitable for traversing the set.
This is the highest level of grouping for TextureImages.
Definition: paletteGroup.h:47
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:35
static QualityLevel string_quality_level(const string &string)
Returns the TexGen value associated with the given string representation, or ET_unspecified if the st...
void set_case_sensitive(bool case_sensitive)
Sets whether the match is case sensitive (true) or case insensitive (false).
Definition: globPattern.I:112
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc. ...
static Format string_format(const string &string)
Returns the Format value associated with the given string representation, or F_unspecified if the str...
Definition: eggTexture.cxx:733
void match_txa_groups(const PaletteGroups &groups)
Adds the indicated set of groups, read from the .txa file, to the set of groups to which the egg file...
Definition: eggFile.cxx:252
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:93
bool match_texture(TextureImage *texture) const
Compares the patterns on the line to the indicated TextureImage.
Definition: txaLine.cxx:403
bool get_size()
Determines the size of the SourceTextureImage, if it is not already known.
This is a texture image reference as it appears in an egg file: the source image of the texture...
bool match_egg(EggFile *egg_file) const
Compares the patterns on the line to the indicated EggFile.
Definition: txaLine.cxx:340
void make_union(const PaletteGroups &a, const PaletteGroups &b)
Computes the union of PaletteGroups a and b, and stores the result in this object.
void insert(PaletteGroup *group)
Inserts a new group to the set, if it is not already there.
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:51
bool parse(const string &line)
Accepts a string that defines a line of the .txa file and parses it into its constinuent parts...
Definition: txaLine.cxx:63
These are the things that a user might explicitly request to adjust on a texture via a line in the ...
This represents a single egg file known to the palettizer.
Definition: eggFile.h:39
virtual string get_suggested_extension() const
Returns a suitable filename extension (without a leading dot) to suggest for files of this type...
Definition: pnmFileType.cxx:75
This class can be used to test for string matches against standard Unix-shell filename globbing conve...
Definition: globPattern.h:37