Browse Source

texture: support store() on more component types (incl. sRGB)

This changes behaviour for sRGB textures, which weren't previously converting to the correct color space.

Also add unit tests for storing to PNMImage.

Closes: #212
rdb 8 years ago
parent
commit
9dcfcbf5fa
4 changed files with 178 additions and 31 deletions
  1. 85 29
      panda/src/gobj/texture.cxx
  2. 2 1
      panda/src/gobj/texture.h
  3. 1 1
      panda/src/pnmimage/pnmImage.I
  4. 90 0
      tests/gobj/test_texture.py

+ 85 - 29
panda/src/gobj/texture.cxx

@@ -5088,7 +5088,8 @@ do_store_one(CData *cdata, PNMImage &pnmimage, int z, int n) {
   return convert_to_pnmimage(pnmimage,
   return convert_to_pnmimage(pnmimage,
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_x_size(cdata, n),
                              do_get_expected_mipmap_y_size(cdata, n),
                              do_get_expected_mipmap_y_size(cdata, n),
-                             cdata->_num_components, cdata->_component_width,
+                             cdata->_num_components, cdata->_component_type,
+                             is_srgb(cdata->_format),
                              cdata->_ram_images[n]._image,
                              cdata->_ram_images[n]._image,
                              do_get_ram_mipmap_page_size(cdata, n), z);
                              do_get_ram_mipmap_page_size(cdata, n), z);
 }
 }
@@ -5111,12 +5112,14 @@ do_store_one(CData *cdata, PfmFile &pfm, int z, int n) {
   if (cdata->_component_type != T_float) {
   if (cdata->_component_type != T_float) {
     // PfmFile by way of PNMImage.
     // PfmFile by way of PNMImage.
     PNMImage pnmimage;
     PNMImage pnmimage;
-    bool success = convert_to_pnmimage(pnmimage,
-                                       do_get_expected_mipmap_x_size(cdata, n),
-                                       do_get_expected_mipmap_y_size(cdata, n),
-                                       cdata->_num_components, cdata->_component_width,
-                                       cdata->_ram_images[n]._image,
-                                       do_get_ram_mipmap_page_size(cdata, n), z);
+    bool success =
+      convert_to_pnmimage(pnmimage,
+                          do_get_expected_mipmap_x_size(cdata, n),
+                          do_get_expected_mipmap_y_size(cdata, n),
+                          cdata->_num_components, cdata->_component_type,
+                          is_srgb(cdata->_format),
+                          cdata->_ram_images[n]._image,
+                          do_get_ram_mipmap_page_size(cdata, n), z);
     if (!success) {
     if (!success) {
       return false;
       return false;
     }
     }
@@ -8055,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z,
  */
  */
 bool Texture::
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                    int num_components, int component_width,
-                    CPTA_uchar image, size_t page_size, int z) {
+                    int num_components, ComponentType component_type,
+                    bool is_srgb, CPTA_uchar image, size_t page_size, int z) {
   xelval maxval = 0xff;
   xelval maxval = 0xff;
-  if (component_width > 1) {
+  if (component_type != T_unsigned_byte && component_type != T_byte) {
     maxval = 0xffff;
     maxval = 0xffff;
   }
   }
-  pnmimage.clear(x_size, y_size, num_components, maxval);
+  ColorSpace color_space = is_srgb ? CS_sRGB : CS_linear;
+  pnmimage.clear(x_size, y_size, num_components, maxval, nullptr, color_space);
   bool has_alpha = pnmimage.has_alpha();
   bool has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
   bool is_grayscale = pnmimage.is_grayscale();
 
 
   int idx = page_size * z;
   int idx = page_size * z;
   nassertr(idx + page_size <= image.size(), false);
   nassertr(idx + page_size <= image.size(), false);
-  const unsigned char *p = &image[idx];
 
 
-  if (component_width == 1) {
-    xel *array = pnmimage.get_array();
+  xel *array = pnmimage.get_array();
+  xelval *alpha = pnmimage.get_alpha_array();
+
+  switch (component_type) {
+  case T_unsigned_byte:
     if (is_grayscale) {
     if (is_grayscale) {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8090,9 +8096,10 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
           }
         }
         }
       }
       }
+      nassertr(p == &image[idx] + page_size, false);
     } else {
     } else {
+      const unsigned char *p = &image[idx];
       if (has_alpha) {
       if (has_alpha) {
-        xelval *alpha = pnmimage.get_alpha_array();
         for (int j = y_size-1; j >= 0; j--) {
         for (int j = y_size-1; j >= 0; j--) {
           xel *row = array + j * x_size;
           xel *row = array + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
           xelval *alpha_row = alpha + j * x_size;
@@ -8113,29 +8120,78 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
           }
           }
         }
         }
       }
       }
+      nassertr(p == &image[idx] + page_size, false);
     }
     }
+    break;
 
 
-  } else if (component_width == 2) {
-    for (int j = y_size-1; j >= 0; j--) {
-      for (int i = 0; i < x_size; i++) {
-        if (is_grayscale) {
-          pnmimage.set_gray(i, j, get_unsigned_short(p));
-        } else {
-          pnmimage.set_blue(i, j, get_unsigned_short(p));
-          pnmimage.set_green(i, j, get_unsigned_short(p));
-          pnmimage.set_red(i, j, get_unsigned_short(p));
+  case T_unsigned_short:
+    {
+      const uint16_t *p = (const uint16_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], *p++);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], *p++);
+            PPM_PUTR(row[i], *p++);
+          }
+          if (has_alpha) {
+            alpha_row[i] = *p++;
+          }
         }
         }
-        if (has_alpha) {
-          pnmimage.set_alpha(i, j, get_unsigned_short(p));
+      }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
+    }
+    break;
+
+  case T_unsigned_int:
+    {
+      const uint32_t *p = (const uint32_t *)&image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        xel *row = array + j * x_size;
+        xelval *alpha_row = alpha + j * x_size;
+        for (int i = 0; i < x_size; i++) {
+          PPM_PUTB(row[i], (*p++) >> 16u);
+          if (!is_grayscale) {
+            PPM_PUTG(row[i], (*p++) >> 16u);
+            PPM_PUTR(row[i], (*p++) >> 16u);
+          }
+          if (has_alpha) {
+            alpha_row[i] = (*p++) >> 16u;
+          }
         }
         }
       }
       }
+      nassertr((const unsigned char *)p == &image[idx] + page_size, false);
     }
     }
+    break;
 
 
-  } else {
+  case T_half_float:
+    {
+      const unsigned char *p = &image[idx];
+
+      for (int j = y_size-1; j >= 0; j--) {
+        for (int i = 0; i < x_size; i++) {
+          pnmimage.set_blue(i, j, get_half_float(p));
+          if (!is_grayscale) {
+            pnmimage.set_green(i, j, get_half_float(p));
+            pnmimage.set_red(i, j, get_half_float(p));
+          }
+          if (has_alpha) {
+            pnmimage.set_alpha(i, j, get_half_float(p));
+          }
+        }
+      }
+      nassertr(p == &image[idx] + page_size, false);
+    }
+    break;
+
+  default:
     return false;
     return false;
   }
   }
 
 
-  nassertr(p == &image[idx] + page_size, false);
   return true;
   return true;
 }
 }
 
 

+ 2 - 1
panda/src/gobj/texture.h

@@ -801,7 +801,8 @@ private:
                                int z, const PfmFile &pfm,
                                int z, const PfmFile &pfm,
                                int num_components, int component_width);
                                int num_components, int component_width);
   static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
   static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                                  int num_components, int component_width,
+                                  int num_components,
+                                  ComponentType component_type, bool is_srgb,
                                   CPTA_uchar image, size_t page_size,
                                   CPTA_uchar image, size_t page_size,
                                   int z);
                                   int z);
   static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,
   static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size,

+ 1 - 1
panda/src/pnmimage/pnmImage.I

@@ -80,7 +80,7 @@ to_val(float input_value) const {
   switch (_xel_encoding) {
   switch (_xel_encoding) {
   case XE_generic:
   case XE_generic:
   case XE_generic_alpha:
   case XE_generic_alpha:
-    return clamp_val((int)(input_value * get_maxval() + 0.5f));
+    return (int)(min(1.0f, max(0.0f, input_value)) * get_maxval() + 0.5f);
 
 
   case XE_generic_sRGB:
   case XE_generic_sRGB:
   case XE_generic_sRGB_alpha:
   case XE_generic_sRGB_alpha:

+ 90 - 0
tests/gobj/test_texture.py

@@ -0,0 +1,90 @@
+from panda3d.core import Texture, PNMImage
+from array import array
+
+
+def image_from_stored_pixel(component_type, format, data):
+    """ Creates a 1-pixel texture with the given settings and pixel data,
+    then returns a PNMImage as result of calling texture.store(). """
+
+    tex = Texture("")
+    tex.setup_1d_texture(1, component_type, format)
+    tex.set_ram_image(data)
+
+    img = PNMImage()
+    assert tex.store(img)
+    return img
+
+
+def test_texture_store_unsigned_byte():
+    data = array('B', (2, 1, 0, 0xff))
+    img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_rgba, data)
+
+    assert img.maxval == 0xff
+    pix = img.get_pixel(0, 0)
+    assert tuple(pix) == (0, 1, 2, 0xff)
+
+
+def test_texture_store_unsigned_short():
+    data = array('H', (2, 1, 0, 0xffff))
+    img = image_from_stored_pixel(Texture.T_unsigned_short, Texture.F_rgba, data)
+
+    assert img.maxval == 0xffff
+    pix = img.get_pixel(0, 0)
+    assert tuple(pix) == (0, 1, 2, 0xffff)
+
+
+def test_texture_store_unsigned_int():
+    data = array('I', (0x30000, 2, 0, 0xffffffff))
+    img = image_from_stored_pixel(Texture.T_unsigned_int, Texture.F_rgba, data)
+
+    assert img.maxval == 0xffff
+    pix = img.get_pixel(0, 0)
+    assert tuple(pix) == (0, 0, 3, 0xffff)
+
+
+def test_texture_store_float():
+    data = array('f', (0.5, 0.0, -2.0, 10000.0))
+    img = image_from_stored_pixel(Texture.T_float, Texture.F_rgba, data)
+
+    assert img.maxval == 0xffff
+    col = img.get_xel_a(0, 0)
+    assert col.almost_equal((0.0, 0.0, 0.5, 1.0), 1 / 255.0)
+
+
+def test_texture_store_half():
+    # Python's array class doesn't support half floats, so we hardcode the
+    # binary representation of these numbers:
+    data = array('H', (
+        # -[exp][mantissa]
+        0b0011110000000000, # 1.0
+        0b1100000000000000, # -2.0
+        0b0111101111111111, # 65504.0
+        0b0011010101010101, # 0.333251953125
+    ))
+    img = image_from_stored_pixel(Texture.T_half_float, Texture.F_rgba, data)
+
+    assert img.maxval == 0xffff
+    col = img.get_xel_a(0, 0)
+    assert col.almost_equal((1.0, 0.0, 1.0, 0.333251953125), 1 / 255.0)
+
+
+def test_texture_store_srgb():
+    # 188 = roughly middle gray
+    data = array('B', [188, 188, 188])
+    img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_srgb, data)
+
+    # We allow some imprecision.
+    assert img.maxval == 0xff
+    col = img.get_xel(0, 0)
+    assert col.almost_equal((0.5, 0.5, 0.5), 1 / 255.0)
+
+
+def test_texture_store_srgb_alpha():
+    # 188 = middle gray
+    data = array('B', [188, 188, 188, 188])
+    img = image_from_stored_pixel(Texture.T_unsigned_byte, Texture.F_srgb_alpha, data)
+
+    # We allow some imprecision.
+    assert img.maxval == 0xff
+    col = img.get_xel_a(0, 0)
+    assert col.almost_equal((0.5, 0.5, 0.5, 188 / 255.0), 1 / 255.0)