diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 29b1702280..841c8aa9bf 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -2324,6 +2324,61 @@ get_unsigned_short(const unsigned char *&p) { return (double)v.us / 65535.0; } +/** + * This is used by store() to retrieve the next consecutive component value + * from the indicated element of the array, which is taken to be an array of + * unsigned ints. + */ +INLINE double Texture:: +get_unsigned_int(const unsigned char *&p) { + union { + unsigned int ui; + uchar uc[4]; + } v; + v.uc[0] = (*p++); + v.uc[1] = (*p++); + v.uc[2] = (*p++); + v.uc[3] = (*p++); + return (double)v.ui / 4294967295.0; +} + +/** + * This is used by store() to retrieve the next consecutive component value + * from the indicated element of the array, which is taken to be an array of + * floats. + */ +INLINE double Texture:: +get_float(const unsigned char *&p) { + double v = *((float *)p); + p += 4; + return v; +} + +/** + * This is used by store() to retrieve the next consecutive component value + * from the indicated element of the array, which is taken to be an array of + * half-floats. + */ +INLINE double Texture:: +get_half_float(const unsigned char *&p) { + union { + uint32_t ui; + float uf; + } v; + uint16_t in = *(uint16_t *)p; + p += 2; + uint32_t t1 = in & 0x7fff; // Non-sign bits + uint32_t t2 = in & 0x8000; // Sign bit + uint32_t t3 = in & 0x7c00; // Exponent + t1 <<= 13; // Align mantissa on MSB + t2 <<= 16; // Shift sign bit into position + t1 += 0x38000000; // Adjust bias + t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero + t1 |= t2; // Re-insert sign bit + v.ui = t1; + return v.uf; +} + /** * Returns true if the indicated filename ends in .txo or .txo.pz or .txo.gz, * false otherwise. diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 6b649b9ba5..60c16b3675 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -856,6 +856,9 @@ private: INLINE static void store_scaled_short(unsigned char *&p, int value, double scale); INLINE static double get_unsigned_byte(const unsigned char *&p); INLINE static double get_unsigned_short(const unsigned char *&p); + INLINE static double get_unsigned_int(const unsigned char *&p); + INLINE static double get_float(const unsigned char *&p); + INLINE static double get_half_float(const unsigned char *&p); INLINE static bool is_txo_filename(const Filename &fullpath); INLINE static bool is_dds_filename(const Filename &fullpath); diff --git a/panda/src/gobj/texturePeeker.cxx b/panda/src/gobj/texturePeeker.cxx index 42e619e501..b4e1be9224 100644 --- a/panda/src/gobj/texturePeeker.cxx +++ b/panda/src/gobj/texturePeeker.cxx @@ -82,6 +82,18 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { _get_component = Texture::get_unsigned_short; break; + case Texture::T_unsigned_int: + _get_component = Texture::get_unsigned_int; + break; + + case Texture::T_float: + _get_component = Texture::get_float; + break; + + case Texture::T_half_float: + _get_component = Texture::get_half_float; + break; + default: // Not supported. _image.clear(); @@ -123,7 +135,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { break; case Texture::F_rgb: - case Texture::F_srgb: case Texture::F_rgb5: case Texture::F_rgb8: case Texture::F_rgb12: @@ -135,7 +146,6 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { break; case Texture::F_rgba: - case Texture::F_srgb_alpha: case Texture::F_rgbm: case Texture::F_rgba4: case Texture::F_rgba5: @@ -146,6 +156,25 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { case Texture::F_rgb10_a2: _get_texel = get_texel_rgba; break; + + case Texture::F_srgb: + if (_component_type == Texture::T_unsigned_byte) { + _get_texel = get_texel_srgb; + } else { + gobj_cat.error() + << "sRGB texture should have component type T_unsigned_byte\n"; + } + break; + + case Texture::F_srgb_alpha: + if (_component_type == Texture::T_unsigned_byte) { + _get_texel = get_texel_srgba; + } else { + gobj_cat.error() + << "sRGB texture should have component type T_unsigned_byte\n"; + } + break; + default: // Not supported. gobj_cat.error() << "Unsupported texture peeker format: " @@ -570,3 +599,27 @@ get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_com color[0] = (*get_component)(p); color[3] = (*get_component)(p); } + +/** + * Gets the color of the texel at byte p, given that the texture is in format + * F_srgb or similar. + */ +void TexturePeeker:: +get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) { + color[2] = decode_sRGB_float(*p++); + color[1] = decode_sRGB_float(*p++); + color[0] = decode_sRGB_float(*p++); + color[3] = 1.0f; +} + +/** + * Gets the color of the texel at byte p, given that the texture is in format + * F_srgb_alpha or similar. + */ +void TexturePeeker:: +get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) { + color[2] = decode_sRGB_float(*p++); + color[1] = decode_sRGB_float(*p++); + color[0] = decode_sRGB_float(*p++); + color[3] = (*get_component)(p); +} diff --git a/panda/src/gobj/texturePeeker.h b/panda/src/gobj/texturePeeker.h index cb503bf041..ffe3aba2b1 100644 --- a/panda/src/gobj/texturePeeker.h +++ b/panda/src/gobj/texturePeeker.h @@ -79,6 +79,8 @@ private: static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); + static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); + static void get_texel_srgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); int _x_size; int _y_size; diff --git a/tests/gobj/test_texture_peek.py b/tests/gobj/test_texture_peek.py new file mode 100644 index 0000000000..accd1b02d0 --- /dev/null +++ b/tests/gobj/test_texture_peek.py @@ -0,0 +1,96 @@ +from panda3d.core import Texture, LColor +from array import array + + +def peeker_from_pixel(component_type, format, data): + """ Creates a 1-pixel texture with the given settings and pixel data, + then returns a TexturePeeker as result of calling texture.peek(). """ + + tex = Texture("") + tex.setup_1d_texture(1, component_type, format) + tex.set_ram_image(data) + peeker = tex.peek() + assert peeker.has_pixel(0, 0) + return peeker + + +def test_texture_peek_ubyte(): + maxval = 255 + data = array('B', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_ushort(): + maxval = 65535 + data = array('H', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_short, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_uint(): + maxval = 4294967295 + data = array('I', (2, 1, 0, maxval)) + peeker = peeker_from_pixel(Texture.T_unsigned_int, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + col *= maxval + assert col == (0, 1, 2, maxval) + + +def test_texture_peek_float(): + data = array('f', (1.0, 0.0, -2.0, 10000.0)) + peeker = peeker_from_pixel(Texture.T_float, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + assert col == (-2.0, 0.0, 1.0, 10000.0) + + +def test_texture_peek_half(): + # Python's array class doesn't support half floats, so we hardcode the + # binary representation of these numbers: + data = array('H', ( + 0b0011110000000000, # 1.0 + 0b1100000000000000, # -2.0 + 0b0111101111111111, # 65504.0 + 0b0011010101010101, # 0.333251953125 + )) + peeker = peeker_from_pixel(Texture.T_half_float, Texture.F_rgba, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + assert col == (65504.0, -2.0, 1.0, 0.333251953125) + + +def test_texture_peek_srgb(): + # 188 = roughly middle gray + data = array('B', [188, 188, 188]) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_srgb, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + + # We allow some imprecision. + assert col.almost_equal((0.5, 0.5, 0.5, 1.0), 1 / 255.0) + + +def test_texture_peek_srgba(): + # 188 = middle gray + data = array('B', [188, 188, 188, 188]) + peeker = peeker_from_pixel(Texture.T_unsigned_byte, Texture.F_srgb_alpha, data) + + col = LColor() + peeker.fetch_pixel(col, 0, 0) + + # We allow some imprecision. + assert col.almost_equal((0.5, 0.5, 0.5, 188 / 255.0), 1 / 255.0)