diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index c65172364e..c4a8149ab4 100644 --- a/panda/src/gobj/texture.cxx +++ b/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, do_get_expected_mipmap_x_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, 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) { // PfmFile by way of 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) { return false; } @@ -8055,25 +8058,28 @@ convert_from_pfm(PTA_uchar &image, size_t page_size, int z, */ bool Texture:: 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; - if (component_width > 1) { + if (component_type != T_unsigned_byte && component_type != T_byte) { 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 is_grayscale = pnmimage.is_grayscale(); int idx = page_size * z; 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) { + const unsigned char *p = &image[idx]; if (has_alpha) { - xelval *alpha = pnmimage.get_alpha_array(); for (int j = y_size-1; j >= 0; j--) { xel *row = array + 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 { + const unsigned char *p = &image[idx]; if (has_alpha) { - xelval *alpha = pnmimage.get_alpha_array(); for (int j = y_size-1; j >= 0; j--) { xel *row = array + 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)); - } - if (has_alpha) { - pnmimage.set_alpha(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++; + } } } + nassertr((const unsigned char *)p == &image[idx] + page_size, false); } + break; - } else { + 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; + + 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; } - nassertr(p == &image[idx] + page_size, false); return true; } diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 49e2f48dbd..5816b8c538 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -801,7 +801,8 @@ private: int z, const PfmFile &pfm, int num_components, int component_width); 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, int z); static bool convert_to_pfm(PfmFile &pfm, int x_size, int y_size, diff --git a/panda/src/pnmimage/pnmImage.I b/panda/src/pnmimage/pnmImage.I index 173f543e1e..d4a7026483 100644 --- a/panda/src/pnmimage/pnmImage.I +++ b/panda/src/pnmimage/pnmImage.I @@ -80,7 +80,7 @@ to_val(float input_value) const { switch (_xel_encoding) { case XE_generic: 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_alpha: diff --git a/tests/gobj/test_texture.py b/tests/gobj/test_texture.py new file mode 100644 index 0000000000..8fa0ed27c8 --- /dev/null +++ b/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)