|
"""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 |
|
|
|
@property |
|
def name(self): |
|
"""str : The user-defined name of this object. |
|
""" |
|
return self._name |
|
|
|
@name.setter |
|
def name(self, value): |
|
if value is not None: |
|
value = str(value) |
|
self._name = value |
|
|
|
@property |
|
def sampler(self): |
|
""":class:`Sampler` : The sampler used by this texture. |
|
""" |
|
return self._sampler |
|
|
|
@sampler.setter |
|
def sampler(self, value): |
|
if value is None: |
|
value = Sampler() |
|
self._sampler = value |
|
|
|
@property |
|
def source(self): |
|
"""(h,w,c) uint8 or float or :class:`PIL.Image.Image` : The image |
|
used in this texture. |
|
""" |
|
return self._source |
|
|
|
@source.setter |
|
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 |
|
|
|
@property |
|
def source_channels(self): |
|
"""str : The channels that were extracted from the original source. |
|
""" |
|
return self._source_channels |
|
|
|
@source_channels.setter |
|
def source_channels(self, value): |
|
self._source_channels = value |
|
|
|
@property |
|
def width(self): |
|
"""int : The width of the texture buffer. |
|
""" |
|
return self._width |
|
|
|
@width.setter |
|
def width(self, value): |
|
self._width = value |
|
|
|
@property |
|
def height(self): |
|
"""int : The height of the texture buffer. |
|
""" |
|
return self._height |
|
|
|
@height.setter |
|
def height(self, value): |
|
self._height = value |
|
|
|
@property |
|
def tex_type(self): |
|
"""int : The type of the texture. |
|
""" |
|
return self._tex_type |
|
|
|
@tex_type.setter |
|
def tex_type(self, value): |
|
self._tex_type = value |
|
|
|
@property |
|
def data_format(self): |
|
"""int : The format of the texture data. |
|
""" |
|
return self._data_format |
|
|
|
@data_format.setter |
|
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() |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
self._texid = glGenTextures(1) |
|
glBindTexture(self.tex_type, self._texid) |
|
|
|
|
|
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] |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
glBindTexture(self.tex_type, 0) |
|
|
|
def _remove_from_context(self): |
|
if self._texid is not None: |
|
|
|
|
|
glDeleteTextures([self._texid]) |
|
self._texid = None |
|
|
|
def _in_context(self): |
|
return self._texid is not None |
|
|
|
def _bind(self): |
|
|
|
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) |
|
|