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
This commit is contained in:
rdb 2017-12-19 20:02:10 +01:00
parent e9a50c8898
commit 9dcfcbf5fa
4 changed files with 179 additions and 32 deletions

View File

@ -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;
}

View File

@ -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,

View File

@ -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:

View File

@ -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)