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  */
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  */
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  */
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 }
PaletteGroups::make_union
void make_union(const PaletteGroups &a, const PaletteGroups &b)
Computes the union of PaletteGroups a and b, and stores the result in this object.
Definition: paletteGroups.cxx:91
ImageFile::get_y_size
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:92
EggFile::match_txa_groups
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
PaletteGroup
This is the highest level of grouping for TextureImages.
Definition: paletteGroup.h:43
EggFile
This represents a single egg file known to the palettizer.
Definition: eggFile.h:36
pvector< int >
TxaLine::parse
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
EggFile::clear_surprise
void clear_surprise()
Removes the 'surprise' flag; this file has been successfully matched against a line in the ....
Definition: eggFile.cxx:285
TxaLine::match_texture
bool match_texture(TextureImage *texture) const
Compares the patterns on the line to the indicated TextureImage.
Definition: txaLine.cxx:388
textureImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMFileType::get_suggested_extension
get_suggested_extension
Returns a suitable filename extension (without a leading dot) to suggest for files of this type,...
Definition: pnmFileType.h:49
pnotify.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SourceTextureImage
This is a texture image reference as it appears in an egg file: the source image of the texture.
Definition: sourceTextureImage.h:28
PaletteGroups::remove_null
void remove_null()
Removes the special "null" group from the set.
Definition: paletteGroups.cxx:162
PaletteGroups::insert
void insert(PaletteGroup *group)
Inserts a new group to the set, if it is not already there.
Definition: paletteGroups.cxx:55
TextureRequest
These are the things that a user might explicitly request to adjust on a texture via a line in the ....
Definition: textureRequest.h:28
SourceTextureImage::get_size
bool get_size()
Determines the size of the SourceTextureImage, if it is not already known.
Definition: sourceTextureImage.cxx:87
palettizer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteGroups::begin
iterator begin() const
Returns an iterator suitable for traversing the set.
Definition: paletteGroups.cxx:200
pnmFileType.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string_to_int
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
Definition: string_utils.cxx:324
sourceTextureImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
txaLine.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string_to_double
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
Definition: string_utils.cxx:354
TxaLine::match_egg
bool match_egg(EggFile *egg_file) const
Compares the patterns on the line to the indicated EggFile.
Definition: txaLine.cxx:330
EggRenderMode::string_alpha_mode
static AlphaMode string_alpha_mode(const std::string &string)
Returns the AlphaMode value associated with the given string representation, or AM_unspecified if the...
Definition: eggRenderMode.cxx:173
extract_words
int extract_words(const string &str, vector_string &words)
Divides the string into a number of words according to whitespace.
Definition: string_utils.cxx:105
pal_string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Palettizer::test_palette_group
PaletteGroup * test_palette_group(const std::string &name) const
Returns the PaletteGroup with the given name.
Definition: palettizer.cxx:810
ImageFile::get_x_size
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:82
EggTexture::string_quality_level
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...
Definition: eggTexture.cxx:1029
EggTexture::string_wrap_mode
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:773
eggFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggTexture::string_format
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:693
TextureImage::get_preferred_source
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc.
Definition: textureImage.cxx:547
GlobPattern
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition: globPattern.h:32
paletteGroup.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PaletteGroups::end
iterator end() const
Returns an iterator suitable for traversing the set.
Definition: paletteGroups.cxx:208
TextureImage
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:46
GlobPattern::set_case_sensitive
set_case_sensitive
Sets whether the match is case sensitive (true) or case insensitive (false).
Definition: globPattern.h:48