Panda3D
texturePeeker.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 texturePeeker.cxx
10  * @author drose
11  * @date 2008-08-26
12  */
13 
14 #include "texturePeeker.h"
15 
16 
17 /**
18  * Use Texture::peek() to construct a TexturePeeker.
19  *
20  * This constructor is called only by Texture::peek(), and assumes the
21  * texture's lock is already held.
22  */
23 TexturePeeker::
24 TexturePeeker(Texture *tex, Texture::CData *cdata) {
25  if (cdata->_texture_type == Texture::TT_cube_map) {
26  // Cube map texture. We'll need to map from (u, v, w) to (u, v) within
27  // the appropriate page, where w indicates the page.
28 
29  // TODO: handle cube maps.
30  return;
31 
32  } else {
33  // Regular 1-d, 2-d, or 3-d texture. The coordinates map directly.
34  // Simple ram images are possible if it is a 2-d texture.
35  if (tex->do_has_ram_image(cdata) && cdata->_ram_image_compression == Texture::CM_off) {
36  // Get the regular RAM image if it is available.
37  _image = tex->do_get_ram_image(cdata);
38  _x_size = cdata->_x_size;
39  _y_size = cdata->_y_size;
40  _z_size = cdata->_z_size;
41  _component_width = cdata->_component_width;
42  _num_components = cdata->_num_components;
43  _format = cdata->_format;
44  _component_type = cdata->_component_type;
45 
46  } else if (!cdata->_simple_ram_image._image.empty()) {
47  // Get the simple RAM image if *that* is available.
48  _image = cdata->_simple_ram_image._image;
49  _x_size = cdata->_simple_x_size;
50  _y_size = cdata->_simple_y_size;
51  _z_size = 1;
52 
53  _component_width = 1;
54  _num_components = 4;
55  _format = Texture::F_rgba;
56  _component_type = Texture::T_unsigned_byte;
57 
58  } else {
59  // Failing that, reload and get the uncompressed RAM image.
60  _image = tex->do_get_uncompressed_ram_image(cdata);
61  _x_size = cdata->_x_size;
62  _y_size = cdata->_y_size;
63  _z_size = cdata->_z_size;
64  _component_width = cdata->_component_width;
65  _num_components = cdata->_num_components;
66  _format = cdata->_format;
67  _component_type = cdata->_component_type;
68  }
69  }
70 
71  if (_image.is_null()) {
72  return;
73  }
74  _pixel_width = _component_width * _num_components;
75 
76  switch (_component_type) {
77  case Texture::T_unsigned_byte:
78  _get_component = Texture::get_unsigned_byte;
79  break;
80 
81  case Texture::T_unsigned_short:
82  _get_component = Texture::get_unsigned_short;
83  break;
84 
85  case Texture::T_unsigned_int:
86  _get_component = Texture::get_unsigned_int;
87  break;
88 
89  case Texture::T_float:
90  _get_component = Texture::get_float;
91  break;
92 
93  case Texture::T_half_float:
94  _get_component = Texture::get_half_float;
95  break;
96 
97  case Texture::T_unsigned_int_24_8:
98  _get_component = Texture::get_unsigned_int_24;
99  break;
100 
101  default:
102  // Not supported.
103  _image.clear();
104  return;
105  }
106 
107  switch (_format) {
108  case Texture::F_depth_stencil:
109  case Texture::F_depth_component:
110  case Texture::F_depth_component16:
111  case Texture::F_depth_component24:
112  case Texture::F_depth_component32:
113  case Texture::F_red:
114  case Texture::F_r16:
115  case Texture::F_r32:
116  case Texture::F_r32i:
117  _get_texel = get_texel_r;
118  break;
119 
120  case Texture::F_green:
121  _get_texel = get_texel_g;
122  break;
123 
124  case Texture::F_blue:
125  _get_texel = get_texel_b;
126  break;
127 
128  case Texture::F_alpha:
129  _get_texel = get_texel_a;
130  break;
131 
132  case Texture::F_luminance:
133  case Texture::F_sluminance:
134  _get_texel = get_texel_l;
135  break;
136 
137  case Texture::F_luminance_alpha:
138  case Texture::F_sluminance_alpha:
139  case Texture::F_luminance_alphamask:
140  _get_texel = get_texel_la;
141  break;
142 
143  case Texture::F_rg16:
144  case Texture::F_rg32:
145  case Texture::F_rg:
146  _get_texel = get_texel_rg;
147  break;
148 
149  case Texture::F_rgb:
150  case Texture::F_rgb5:
151  case Texture::F_rgb8:
152  case Texture::F_rgb12:
153  case Texture::F_rgb16:
154  case Texture::F_rgb332:
155  case Texture::F_r11_g11_b10:
156  case Texture::F_rgb9_e5:
157  case Texture::F_rgb32:
158  _get_texel = get_texel_rgb;
159  break;
160 
161  case Texture::F_rgba:
162  case Texture::F_rgbm:
163  case Texture::F_rgba4:
164  case Texture::F_rgba5:
165  case Texture::F_rgba8:
166  case Texture::F_rgba12:
167  case Texture::F_rgba16:
168  case Texture::F_rgba32:
169  case Texture::F_rgb10_a2:
170  _get_texel = get_texel_rgba;
171  break;
172 
173  case Texture::F_srgb:
174  if (_component_type == Texture::T_unsigned_byte) {
175  _get_texel = get_texel_srgb;
176  } else {
177  gobj_cat.error()
178  << "sRGB texture should have component type T_unsigned_byte\n";
179  }
180  break;
181 
182  case Texture::F_srgb_alpha:
183  if (_component_type == Texture::T_unsigned_byte) {
184  _get_texel = get_texel_srgba;
185  } else {
186  gobj_cat.error()
187  << "sRGB texture should have component type T_unsigned_byte\n";
188  }
189  break;
190 
191  default:
192  // Not supported.
193  gobj_cat.error() << "Unsupported texture peeker format: "
194  << Texture::format_format(_format) << std::endl;
195  _image.clear();
196  return;
197  }
198 }
199 
200 
201 /**
202  * Fills "color" with the RGBA color of the texel at point (u, v).
203  *
204  * The texel color is determined via nearest-point sampling (no filtering of
205  * adjacent pixels), regardless of the filter type associated with the
206  * texture. u, v, and w will wrap around regardless of the texture's wrap
207  * mode.
208  */
209 void TexturePeeker::
210 lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
211  int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
212  int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
213  fetch_pixel(color, x, y);
214 }
215 
216 /**
217  * Works like TexturePeeker::lookup(), but instead uv-coordinates integer
218  * coordinates are used.
219  */
220 void TexturePeeker::
221 fetch_pixel(LColor& color, int x, int y) const {
222  nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size);
223  const unsigned char *p = _image.p() + (y * _x_size + x) * _pixel_width;
224  (*_get_texel)(color, p, _get_component);
225 }
226 
227 
228 /**
229  * Performs a bilinear lookup to retrieve the color value stored at the uv
230  * coordinate (u, v).
231  *
232  * In case the point is outside of the uv range, color is set to zero,
233  * and false is returned. Otherwise true is returned.
234  */
235 bool TexturePeeker::
236 lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
237  color = LColor::zero();
238 
239  u = u * _x_size - 0.5;
240  v = v * _y_size - 0.5;
241 
242  int min_u = int(floor(u));
243  int min_v = int(floor(v));
244 
245  PN_stdfloat frac_u = u - min_u;
246  PN_stdfloat frac_v = v - min_v;
247 
248  LColor p00(LColor::zero()), p01(LColor::zero()), p10(LColor::zero()), p11(LColor::zero());
249  PN_stdfloat w00 = 0.0, w01 = 0.0, w10 = 0.0, w11 = 0.0;
250 
251  if (has_pixel(min_u, min_v)) {
252  w00 = (1.0 - frac_v) * (1.0 - frac_u);
253  fetch_pixel(p00, min_u, min_v);
254  }
255  if (has_pixel(min_u + 1, min_v)) {
256  w10 = (1.0 - frac_v) * frac_u;
257  fetch_pixel(p10, min_u + 1, min_v);
258  }
259  if (has_pixel(min_u, min_v + 1)) {
260  w01 = frac_v * (1.0 - frac_u);
261  fetch_pixel(p01, min_u, min_v + 1);
262  }
263  if (has_pixel(min_u + 1, min_v + 1)) {
264  w11 = frac_v * frac_u;
265  fetch_pixel(p11, min_u + 1, min_v + 1);
266  }
267 
268  PN_stdfloat net_w = w00 + w01 + w10 + w11;
269  if (net_w == 0.0) {
270  return false;
271  }
272 
273  color = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11) / net_w;
274  return true;
275 }
276 
277 /**
278  * Fills "color" with the RGBA color of the texel at point (u, v, w).
279  *
280  * The texel color is determined via nearest-point sampling (no filtering of
281  * adjacent pixels), regardless of the filter type associated with the
282  * texture. u, v, and w will wrap around regardless of the texture's wrap
283  * mode.
284  */
285 void TexturePeeker::
286 lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const {
287  int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
288  int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
289  int z = int((w - cfloor(w)) * (PN_stdfloat)_z_size) % _z_size;
290 
291  nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size &&
292  z >= 0 && z < _z_size);
293  const unsigned char *p = _image.p() + (z * _x_size * _y_size + y * _x_size + x) * _pixel_width;
294 
295  (*_get_texel)(color, p, _get_component);
296 }
297 
298 /**
299  * Fills "color" with the average RGBA color of the texels within the
300  * rectangle defined by the specified coordinate range.
301  *
302  * The texel color is linearly filtered over the entire region. u, v, and w
303  * will wrap around regardless of the texture's wrap mode.
304  */
305 void TexturePeeker::
306 filter_rect(LColor &color,
307  PN_stdfloat min_u, PN_stdfloat min_v, PN_stdfloat max_u, PN_stdfloat max_v) const {
308  int min_x, max_x;
309  init_rect_minmax(min_x, max_x, min_u, max_u, _x_size);
310 
311  int min_y, max_y;
312  init_rect_minmax(min_y, max_y, min_v, max_v, _y_size);
313 
314  color.set(0.0f, 0.0f, 0.0f, 0.0f);
315  PN_stdfloat net = 0.0f;
316  accum_filter_y(color, net, 0,
317  min_x, max_x, min_u, max_u,
318  min_y, max_y, min_v, max_v,
319  1.0f);
320 
321  if (net != 0.0f) {
322  color /= net;
323  }
324 }
325 
326 /**
327  * Fills "color" with the average RGBA color of the texels within the
328  * rectangle defined by the specified coordinate range.
329  *
330  * The texel color is linearly filtered over the entire region. u, v, and w
331  * will wrap around regardless of the texture's wrap mode.
332  */
333 void TexturePeeker::
334 filter_rect(LColor &color,
335  PN_stdfloat min_u, PN_stdfloat min_v, PN_stdfloat min_w,
336  PN_stdfloat max_u, PN_stdfloat max_v, PN_stdfloat max_w) const {
337  int min_x, max_x;
338  init_rect_minmax(min_x, max_x, min_u, max_u, _x_size);
339 
340  int min_y, max_y;
341  init_rect_minmax(min_y, max_y, min_v, max_v, _y_size);
342 
343  int min_z, max_z;
344  init_rect_minmax(min_z, max_z, min_w, max_w, _z_size);
345 
346  color.set(0.0f, 0.0f, 0.0f, 0.0f);
347  PN_stdfloat net = 0.0f;
348  accum_filter_z(color, net,
349  min_x, max_x, min_u, max_u,
350  min_y, max_y, min_v, max_v,
351  min_z, max_z, min_w, max_w);
352 
353  if (net != 0.0f) {
354  color /= net;
355  }
356 }
357 
358 /**
359  * Sanity-checks min_u, max_u and computes min_x and min_y based on them.
360  * Also works for y and z.
361  */
362 void TexturePeeker::
363 init_rect_minmax(int &min_x, int &max_x, PN_stdfloat &min_u, PN_stdfloat &max_u,
364  int x_size) {
365  if (min_u > max_u) {
366  PN_stdfloat t = min_u;
367  min_u = max_u;
368  max_u = t;
369  }
370  if (max_u - min_u >= 1.0f) {
371  min_u = 0.0f;
372  max_u = 1.0f;
373  }
374  min_x = (int)cfloor(min_u * (PN_stdfloat)x_size);
375  max_x = (int)cceil(max_u * (PN_stdfloat)x_size);
376  nassertv(min_x <= max_x);
377 }
378 
379 /**
380  * Accumulates the range of pixels from min_z to max_z.
381  */
382 void TexturePeeker::
383 accum_filter_z(LColor &color, PN_stdfloat &net,
384  int min_x, int max_x, PN_stdfloat min_u, PN_stdfloat max_u,
385  int min_y, int max_y, PN_stdfloat min_v, PN_stdfloat max_v,
386  int min_z, int max_z, PN_stdfloat min_w, PN_stdfloat max_w) const {
387  nassertv(min_z >= 0 && min_z <= _z_size &&
388  max_z >= 0 && max_z <= _z_size);
389  int zi = min_z;
390 
391  if (min_z >= max_z - 1) {
392  // Within a single texel.
393  accum_filter_y(color, net, zi % _z_size,
394  min_x, max_x, min_u, max_u,
395  min_y, max_y, min_v, max_v,
396  1.0f);
397 
398  } else {
399  // First part-texel.
400  PN_stdfloat w = (min_z + 1) - min_w * _z_size;
401  accum_filter_y(color, net, zi % _z_size,
402  min_x, max_x, min_u, max_u,
403  min_y, max_y, min_v, max_v,
404  w);
405  int zs = max_z - 1;
406 
407  // Run of full texels.
408  zi = min_z + 1;
409  while (zi < zs) {
410  accum_filter_y(color, net, zi % _z_size,
411  min_x, max_x, min_u, max_u,
412  min_y, max_y, min_v, max_v,
413  1.0f);
414  ++zi;
415  }
416 
417  // Last part-texel.
418  w = max_w * _z_size - (max_z - 1);
419  accum_filter_y(color, net, zi % _z_size,
420  min_x, max_x, min_u, max_u,
421  min_y, max_y, min_v, max_v,
422  w);
423  }
424 }
425 
426 /**
427  * Accumulates the range of pixels from min_y to max_y.
428  */
429 void TexturePeeker::
430 accum_filter_y(LColor &color, PN_stdfloat &net, int zi,
431  int min_x, int max_x, PN_stdfloat min_u, PN_stdfloat max_u,
432  int min_y, int max_y, PN_stdfloat min_v, PN_stdfloat max_v,
433  PN_stdfloat weight) const {
434  nassertv(zi >= 0 && zi < _z_size);
435  nassertv(min_y >= 0 && min_y <= _y_size &&
436  max_y >= 0 && max_y <= _y_size);
437  int yi = min_y;
438 
439  if (min_y >= max_y - 1) {
440  // Within a single texel.
441  accum_filter_x(color, net, yi % _y_size, zi, min_x, max_x, min_u, max_u, weight);
442 
443  } else {
444  // First part-texel.
445  PN_stdfloat w = (min_y + 1) - min_v * _y_size;
446  accum_filter_x(color, net, yi % _y_size, zi, min_x, max_x, min_u, max_u, weight * w);
447  int ys = max_y - 1;
448 
449  // Run of full texels.
450  yi = min_y + 1;
451  while (yi < ys) {
452  accum_filter_x(color, net, yi % _y_size, zi, min_x, max_x, min_u, max_u, weight);
453  ++yi;
454  }
455 
456  // Last part-texel.
457  w = max_v * _y_size - (max_y - 1);
458  accum_filter_x(color, net, yi % _y_size, zi, min_x, max_x, min_u, max_u, weight * w);
459  }
460 }
461 
462 /**
463  * Accumulates the range of pixels from min_x to max_x.
464  */
465 void TexturePeeker::
466 accum_filter_x(LColor &color, PN_stdfloat &net, int yi, int zi,
467  int min_x, int max_x, PN_stdfloat min_u, PN_stdfloat max_u,
468  PN_stdfloat weight) const {
469  nassertv(yi >= 0 && yi < _y_size && zi >= 0 && zi < _z_size);
470  nassertv(min_x >= 0 && min_x <= _x_size &&
471  max_x >= 0 && max_x <= _x_size);
472 
473  // Compute the p corresponding to min_x.
474  int xi = min_x % _x_size;
475  const unsigned char *p = _image.p() + (zi * _x_size * _y_size + yi * _x_size + xi) * _pixel_width;
476 
477  if (min_x >= max_x - 1) {
478  // Within a single texel.
479  accum_texel(color, net, p, weight);
480 
481  } else {
482  // First part-texel.
483  PN_stdfloat w = (min_x + 1) - min_u * _x_size;
484  accum_texel(color, net, p, weight * w);
485  int xs = max_x - 1;
486 
487  // Run of full texels.
488  xi = min_x + 1;
489  while (xi < xs) {
490  if (xi == _x_size) {
491  xi = 0;
492  p = _image.p() + (zi * _x_size * _y_size + yi * _x_size + xi) * _pixel_width;
493  xs -= _x_size;
494  }
495  accum_texel(color, net, p, weight);
496  ++xi;
497  }
498 
499  // Last part-texel.
500  if (xi == _x_size) {
501  xi = 0;
502  p = _image.p() + (zi * _x_size * _y_size + yi * _x_size + xi) * _pixel_width;
503  }
504  w = max_u * _x_size - (max_x - 1);
505  accum_texel(color, net, p, weight * w);
506  }
507 }
508 
509 /**
510  * Accumulates a single texel into the total computed by filter_rect().
511  */
512 void TexturePeeker::
513 accum_texel(LColor &color, PN_stdfloat &net, const unsigned char *&p, PN_stdfloat weight) const {
514  LColor c;
515  (*_get_texel)(c, p, _get_component);
516  color += c * weight;
517  net += weight;
518 }
519 
520 /**
521  * Gets the color of the texel at byte p, given that the texture is in format
522  * F_red.
523  */
524 void TexturePeeker::
525 get_texel_r(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
526  color[0] = (*get_component)(p);
527  color[1] = 0.0f;
528  color[2] = 0.0f;
529  color[3] = 1.0f;
530 }
531 
532 /**
533  * Gets the color of the texel at byte p, given that the texture is in format
534  * F_green.
535  */
536 void TexturePeeker::
537 get_texel_g(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
538  color[0] = 0.0f;
539  color[1] = (*get_component)(p);
540  color[2] = 0.0f;
541  color[3] = 1.0f;
542 }
543 
544 /**
545  * Gets the color of the texel at byte p, given that the texture is in format
546  * F_blue.
547  */
548 void TexturePeeker::
549 get_texel_b(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
550  color[0] = 0.0f;
551  color[1] = 0.0f;
552  color[2] = (*get_component)(p);
553  color[3] = 1.0f;
554 }
555 
556 /**
557  * Gets the color of the texel at byte p, given that the texture is in format
558  * F_alpha.
559  */
560 void TexturePeeker::
561 get_texel_a(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
562  color[0] = 0.0f;
563  color[1] = 0.0f;
564  color[2] = 1.0f;
565  color[3] = (*get_component)(p);
566 }
567 
568 /**
569  * Gets the color of the texel at byte p, given that the texture is in format
570  * F_luminance.
571  */
572 void TexturePeeker::
573 get_texel_l(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
574  color[0] = (*get_component)(p);
575  color[1] = color[0];
576  color[2] = color[0];
577  color[3] = 1.0f;
578 }
579 
580 /**
581  * Gets the color of the texel at byte p, given that the texture is in format
582  * F_luminance_alpha or similar.
583  */
584 void TexturePeeker::
585 get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
586  color[0] = (*get_component)(p);
587  color[1] = color[0];
588  color[2] = color[0];
589  color[3] = (*get_component)(p);
590 }
591 
592 /**
593  * Gets the color of the texel at byte p, given that the texture is in format
594  * F_rg or similar.
595  */
596 void TexturePeeker::
597 get_texel_rg(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
598  color[0] = (*get_component)(p);
599  color[1] = (*get_component)(p);
600  color[2] = 0.0f;
601  color[3] = 1.0f;
602 }
603 
604 /**
605  * Gets the color of the texel at byte p, given that the texture is in format
606  * F_rgb or similar.
607  */
608 void TexturePeeker::
609 get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
610  color[2] = (*get_component)(p);
611  color[1] = (*get_component)(p);
612  color[0] = (*get_component)(p);
613  color[3] = 1.0f;
614 }
615 
616 /**
617  * Gets the color of the texel at byte p, given that the texture is in format
618  * F_rgba or similar.
619  */
620 void TexturePeeker::
621 get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
622  color[2] = (*get_component)(p);
623  color[1] = (*get_component)(p);
624  color[0] = (*get_component)(p);
625  color[3] = (*get_component)(p);
626 }
627 
628 /**
629  * Gets the color of the texel at byte p, given that the texture is in format
630  * F_srgb or similar.
631  */
632 void TexturePeeker::
633 get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
634  color[2] = decode_sRGB_float(*p++);
635  color[1] = decode_sRGB_float(*p++);
636  color[0] = decode_sRGB_float(*p++);
637  color[3] = 1.0f;
638 }
639 
640 /**
641  * Gets the color of the texel at byte p, given that the texture is in format
642  * F_srgb_alpha or similar.
643  */
644 void TexturePeeker::
645 get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) {
646  color[2] = decode_sRGB_float(*p++);
647  color[1] = decode_sRGB_float(*p++);
648  color[0] = decode_sRGB_float(*p++);
649  color[3] = (*get_component)(p);
650 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static std::string format_format(Format f)
Returns the indicated Format converted to a string word.
Definition: texture.cxx:2163
constexpr bool is_null() const
Returns true if the PointerTo is a NULL pointer, false otherwise.
Definition: pointerToVoid.I:27
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const
Fills "color" with the RGBA color of the texel at point (u, v).
void fetch_pixel(LColor &color, int x, int y) const
Works like TexturePeeker::lookup(), but instead uv-coordinates integer coordinates are used.
BEGIN_PUBLISH EXPCL_PANDA_PNMIMAGE float decode_sRGB_float(unsigned char val)
Decodes the sRGB-encoded unsigned char value to a linearized float in the range 0-1.
Definition: convert_srgb.I:18
bool has_pixel(int x, int y) const
Returns whether a given coordinate is inside of the texture dimensions.
Definition: texturePeeker.I:56
const Element * p() const
Function p() is similar to the function from ConstPointerTo.
void clear()
To empty the PTA, use the clear() method, since assignment to NULL is problematic (given the ambiguit...
void filter_rect(LColor &color, PN_stdfloat min_u, PN_stdfloat min_v, PN_stdfloat max_u, PN_stdfloat max_v) const
Fills "color" with the average RGBA color of the texels within the rectangle defined by the specified...
bool lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const
Performs a bilinear lookup to retrieve the color value stored at the uv coordinate (u,...