Spaces:
Runtime error
Runtime error
"""Textures, conforming to the glTF 2.0 standards as specified in | |
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-texture | |
Author: Matthew Matl | |
""" | |
import numpy as np | |
from OpenGL.GL import * | |
from .utils import format_texture_source | |
from .sampler import Sampler | |
class Texture(object): | |
"""A texture and its sampler. | |
Parameters | |
---------- | |
name : str, optional | |
The user-defined name of this object. | |
sampler : :class:`Sampler` | |
The sampler used by this texture. | |
source : (h,w,c) uint8 or (h,w,c) float or :class:`PIL.Image.Image` | |
The image used by this texture. If None, the texture is created | |
empty and width and height must be specified. | |
source_channels : str | |
Either `D`, `R`, `RG`, `GB`, `RGB`, or `RGBA`. Indicates the | |
channels to extract from `source`. Any missing channels will be filled | |
with `1.0`. | |
width : int, optional | |
For empty textures, the width of the texture buffer. | |
height : int, optional | |
For empty textures, the height of the texture buffer. | |
tex_type : int | |
Either GL_TEXTURE_2D or GL_TEXTURE_CUBE. | |
data_format : int | |
For now, just GL_FLOAT. | |
""" | |
def __init__(self, | |
name=None, | |
sampler=None, | |
source=None, | |
source_channels=None, | |
width=None, | |
height=None, | |
tex_type=GL_TEXTURE_2D, | |
data_format=GL_UNSIGNED_BYTE): | |
self.source_channels = source_channels | |
self.name = name | |
self.sampler = sampler | |
self.source = source | |
self.width = width | |
self.height = height | |
self.tex_type = tex_type | |
self.data_format = data_format | |
self._texid = None | |
self._is_transparent = False | |
def name(self): | |
"""str : The user-defined name of this object. | |
""" | |
return self._name | |
def name(self, value): | |
if value is not None: | |
value = str(value) | |
self._name = value | |
def sampler(self): | |
""":class:`Sampler` : The sampler used by this texture. | |
""" | |
return self._sampler | |
def sampler(self, value): | |
if value is None: | |
value = Sampler() | |
self._sampler = value | |
def source(self): | |
"""(h,w,c) uint8 or float or :class:`PIL.Image.Image` : The image | |
used in this texture. | |
""" | |
return self._source | |
def source(self, value): | |
if value is None: | |
self._source = None | |
else: | |
self._source = format_texture_source(value, self.source_channels) | |
self._is_transparent = False | |
def source_channels(self): | |
"""str : The channels that were extracted from the original source. | |
""" | |
return self._source_channels | |
def source_channels(self, value): | |
self._source_channels = value | |
def width(self): | |
"""int : The width of the texture buffer. | |
""" | |
return self._width | |
def width(self, value): | |
self._width = value | |
def height(self): | |
"""int : The height of the texture buffer. | |
""" | |
return self._height | |
def height(self, value): | |
self._height = value | |
def tex_type(self): | |
"""int : The type of the texture. | |
""" | |
return self._tex_type | |
def tex_type(self, value): | |
self._tex_type = value | |
def data_format(self): | |
"""int : The format of the texture data. | |
""" | |
return self._data_format | |
def data_format(self, value): | |
self._data_format = value | |
def is_transparent(self, cutoff=1.0): | |
"""bool : If True, the texture is partially transparent. | |
""" | |
if self._is_transparent is None: | |
self._is_transparent = False | |
if self.source_channels == 'RGBA' and self.source is not None: | |
if np.any(self.source[:,:,3] < cutoff): | |
self._is_transparent = True | |
return self._is_transparent | |
def delete(self): | |
"""Remove this texture from the OpenGL context. | |
""" | |
self._unbind() | |
self._remove_from_context() | |
################## | |
# OpenGL code | |
################## | |
def _add_to_context(self): | |
if self._texid is not None: | |
raise ValueError('Texture already loaded into OpenGL context') | |
fmt = GL_DEPTH_COMPONENT | |
if self.source_channels == 'R': | |
fmt = GL_RED | |
elif self.source_channels == 'RG' or self.source_channels == 'GB': | |
fmt = GL_RG | |
elif self.source_channels == 'RGB': | |
fmt = GL_RGB | |
elif self.source_channels == 'RGBA': | |
fmt = GL_RGBA | |
# Generate the OpenGL texture | |
self._texid = glGenTextures(1) | |
glBindTexture(self.tex_type, self._texid) | |
# Flip data for OpenGL buffer | |
data = None | |
width = self.width | |
height = self.height | |
if self.source is not None: | |
data = np.ascontiguousarray(np.flip(self.source, axis=0).flatten()) | |
width = self.source.shape[1] | |
height = self.source.shape[0] | |
# Bind texture and generate mipmaps | |
glTexImage2D( | |
self.tex_type, 0, fmt, width, height, 0, fmt, | |
self.data_format, data | |
) | |
if self.source is not None: | |
glGenerateMipmap(self.tex_type) | |
if self.sampler.magFilter is not None: | |
glTexParameteri( | |
self.tex_type, GL_TEXTURE_MAG_FILTER, self.sampler.magFilter | |
) | |
else: | |
if self.source is not None: | |
glTexParameteri(self.tex_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR) | |
else: | |
glTexParameteri(self.tex_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST) | |
if self.sampler.minFilter is not None: | |
glTexParameteri( | |
self.tex_type, GL_TEXTURE_MIN_FILTER, self.sampler.minFilter | |
) | |
else: | |
if self.source is not None: | |
glTexParameteri(self.tex_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) | |
else: | |
glTexParameteri(self.tex_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST) | |
glTexParameteri(self.tex_type, GL_TEXTURE_WRAP_S, self.sampler.wrapS) | |
glTexParameteri(self.tex_type, GL_TEXTURE_WRAP_T, self.sampler.wrapT) | |
border_color = 255 * np.ones(4).astype(np.uint8) | |
if self.data_format == GL_FLOAT: | |
border_color = np.ones(4).astype(np.float32) | |
glTexParameterfv( | |
self.tex_type, GL_TEXTURE_BORDER_COLOR, | |
border_color | |
) | |
# Unbind texture | |
glBindTexture(self.tex_type, 0) | |
def _remove_from_context(self): | |
if self._texid is not None: | |
# TODO OPENGL BUG? | |
# glDeleteTextures(1, [self._texid]) | |
glDeleteTextures([self._texid]) | |
self._texid = None | |
def _in_context(self): | |
return self._texid is not None | |
def _bind(self): | |
# TODO HANDLE INDEXING INTO OTHER UV's | |
glBindTexture(self.tex_type, self._texid) | |
def _unbind(self): | |
glBindTexture(self.tex_type, 0) | |
def _bind_as_depth_attachment(self): | |
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, | |
self.tex_type, self._texid, 0) | |
def _bind_as_color_attachment(self): | |
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
self.tex_type, self._texid, 0) | |