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