1
|
|
|
|
2
|
|
|
from PIL import Image |
3
|
|
|
|
4
|
|
|
from ed2d.opengl import gl, pgl |
5
|
|
|
|
6
|
|
|
from ed2d.assets import hdr |
7
|
|
|
|
8
|
|
|
|
9
|
|
|
def load_image(path): |
10
|
|
|
img = Image.open(path) |
11
|
|
|
|
12
|
|
|
# Verify that the image is in RGBA format |
13
|
|
|
if ''.join(img.getbands()) != 'RGBA': |
14
|
|
|
img = img.convert('RGBA') |
15
|
|
|
|
16
|
|
|
# Get image data as a list |
17
|
|
|
data = list(img.getdata()) |
18
|
|
|
|
19
|
|
|
width, height = img.size |
20
|
|
|
return width, height, data |
21
|
|
|
|
22
|
|
|
def load_image_hdr(path): |
23
|
|
|
myhdr = hdr.HDR() |
24
|
|
|
myhdrloader = hdr.HDRLoader() |
25
|
|
|
myhdrloader.load(path, myhdr) |
26
|
|
|
|
27
|
|
|
test = [] |
28
|
|
|
|
29
|
|
|
for i in range(len(myhdr.cols) / 3): |
30
|
|
|
test.append([myhdr.cols[i], myhdr.cols[i + 1], myhdr.cols[i + 2]]) |
31
|
|
|
|
32
|
|
|
return myhdr.width, myhdr.height, test |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
class BaseTexture(object): |
36
|
|
|
''' Texture manager''' |
37
|
|
|
# Just for clarity |
38
|
|
|
# This is basically a static variable |
39
|
|
|
# It will be for assigning each texture unit id easily. |
40
|
|
|
# We use the id for calculating GL_TEXTUREi. This value propagates |
41
|
|
|
# down into the class instances also, even as it is changed. :D |
42
|
|
|
# |
43
|
|
|
# edit: decide if this would be better off as an __init__ method that |
44
|
|
|
# the subclasses call via super. Would be the best thing if we want to |
45
|
|
|
# have external subclasses of the BaseTexture. |
46
|
|
|
|
47
|
|
|
_textureCount = 0 |
48
|
|
|
|
49
|
|
|
def _set_unit_id(self): |
50
|
|
|
self.texUnitID = self._textureCount |
51
|
|
|
BaseTexture._textureCount += 1 |
52
|
|
|
|
53
|
|
|
def __init__(self, program): |
54
|
|
|
|
55
|
|
|
self.program = program |
56
|
|
|
self.texFormat = None |
57
|
|
|
self.pixelType = None |
58
|
|
|
|
59
|
|
|
self._set_unit_id() |
60
|
|
|
|
61
|
|
|
def load_gl(self): |
62
|
|
|
|
63
|
|
|
self.texSampID = self.program.new_uniform(b'textureSampler') |
64
|
|
|
self.texResID = self.program.new_uniform(b'textureResolution') |
65
|
|
|
|
66
|
|
|
if self.texFormat is None: |
67
|
|
|
self.texFormat = gl.GL_RGBA |
68
|
|
|
if self.pixelType is None: |
69
|
|
|
self.pixelType = gl.GL_UNSIGNED_BYTE |
70
|
|
|
|
71
|
|
|
# Load image into new opengl texture |
72
|
|
|
self.texID = pgl.glGenTextures(1) |
73
|
|
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texID) |
74
|
|
|
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) |
75
|
|
|
|
76
|
|
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) |
77
|
|
|
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) |
78
|
|
|
|
79
|
|
|
pgl.glTexImage2D(gl.GL_TEXTURE_2D, 0, self.texFormat, self.width, self.height, |
80
|
|
|
0, self.texFormat, self.pixelType, self.data) |
81
|
|
|
|
82
|
|
|
def bind(self): |
83
|
|
|
gl.glActiveTexture(gl.GL_TEXTURE0 + self.texUnitID) |
84
|
|
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texID) |
85
|
|
|
self.program.set_uniform(self.texSampID, self.texUnitID) |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
class HDRTexture(BaseTexture): |
|
|
|
|
89
|
|
|
|
90
|
|
|
def __init__(self, path, program): |
91
|
|
|
super(Texture, self).__init__(program) |
92
|
|
|
self.path = path |
93
|
|
|
|
94
|
|
|
self.texFormat = gl.GL_RGBA16F |
95
|
|
|
self.pixelType = gl.GL_FLOAT |
96
|
|
|
|
97
|
|
|
self.width, self.height, self.data = load_image_hdr(self.path) |
98
|
|
|
|
99
|
|
|
self.load_gl() |
100
|
|
|
|
101
|
|
|
def load_gl(self): |
102
|
|
|
super(Texture, self).load_gl() |
103
|
|
|
self.program.set_uniform_array(self.texResID, [float(self.width), float(self.height)]) |
104
|
|
|
|
105
|
|
|
class Texture(BaseTexture): |
|
|
|
|
106
|
|
|
|
107
|
|
|
def __init__(self, path, program): |
108
|
|
|
super(Texture, self).__init__(program) |
109
|
|
|
self.path = path |
110
|
|
|
|
111
|
|
|
self.texFormat = gl.GL_RGBA |
112
|
|
|
self.pixelType = gl.GL_UNSIGNED_BYTE |
113
|
|
|
|
114
|
|
|
self.width, self.height, self.data = load_image(self.path) |
115
|
|
|
|
116
|
|
|
self.load_gl() |
117
|
|
|
|
118
|
|
|
def load_gl(self): |
119
|
|
|
super(Texture, self).load_gl() |
120
|
|
|
self.program.set_uniform_array(self.texResID, [float(self.width), float(self.height)]) |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
class TextureAtlas(BaseTexture): |
124
|
|
|
def __init__(self, program, maxWidth=1024, texFormat=None): |
125
|
|
|
super(TextureAtlas, self).__init__(program) |
126
|
|
|
|
127
|
|
|
self.program = program |
128
|
|
|
|
129
|
|
|
self.texFormat = texFormat |
130
|
|
|
|
131
|
|
|
# Data format will be as follows: |
132
|
|
|
# Indexed by textureID |
133
|
|
|
# value: |
134
|
|
|
# - a second dict with information about that texture |
135
|
|
|
# - x, y position in texture, width and height, texture |
136
|
|
|
# data/uvcoords |
137
|
|
|
self.textures = [] |
138
|
|
|
self.data = 0 |
139
|
|
|
self.maxWidth = maxWidth |
140
|
|
|
|
141
|
|
|
self.width = 0 |
142
|
|
|
self.height = 0 |
143
|
|
|
self.cursorPosY = 0 |
144
|
|
|
self.cursorPosX = 0 |
145
|
|
|
self.lineHeight = 0 |
146
|
|
|
self.maxSubTextureHeight = 0 |
147
|
|
|
|
148
|
|
|
def add_texture(self, width, height, texData): |
149
|
|
|
textureID = len(self.textures) |
150
|
|
|
|
151
|
|
|
imgWidth = width |
152
|
|
|
imgHeight = height |
153
|
|
|
|
154
|
|
|
if (self.cursorPosX + imgWidth + 1) >= self.maxWidth: |
155
|
|
|
|
156
|
|
|
self.width = max(self.width, self.cursorPosX) |
157
|
|
|
self.cursorPosY += self.lineHeight |
158
|
|
|
|
159
|
|
|
self.maxSubTextureHeight = max(self.maxSubTextureHeight, self.lineHeight - 1) |
160
|
|
|
|
161
|
|
|
self.lineHeight = 0 |
162
|
|
|
self.cursorPosX = 0 |
163
|
|
|
|
164
|
|
|
x1 = self.cursorPosX |
165
|
|
|
x2 = self.cursorPosX + imgWidth |
166
|
|
|
y1 = self.cursorPosY |
167
|
|
|
y2 = self.cursorPosY + imgHeight |
168
|
|
|
|
169
|
|
|
self.cursorPosX += imgWidth + 1 |
170
|
|
|
self.lineHeight = max(self.lineHeight, imgHeight + 1) |
171
|
|
|
|
172
|
|
|
self.textures.append({ |
173
|
|
|
'x1': x1, 'x2': x2, 'y1': y1, 'y2': y2, |
174
|
|
|
'width': width, 'height': height, |
175
|
|
|
'texData': texData, 'uvCoords': None, |
176
|
|
|
}) |
177
|
|
|
return textureID |
178
|
|
|
|
179
|
|
|
def get_uvcoords(self, texID): |
180
|
|
|
|
181
|
|
|
tex = self.textures[texID] |
182
|
|
|
|
183
|
|
|
x1 = tex['x1'] / float(self.width) |
184
|
|
|
x2 = tex['x2'] / float(self.width) |
185
|
|
|
y1 = tex['y1'] / float(self.height) |
186
|
|
|
y2 = tex['y2'] / float(self.height) |
187
|
|
|
|
188
|
|
|
coord = [[x1, y2], |
189
|
|
|
[x2, y2], |
190
|
|
|
[x1, y1], |
191
|
|
|
[x2, y1]] |
192
|
|
|
|
193
|
|
|
return coord |
194
|
|
|
|
195
|
|
|
def get_vertex_scale(self, texID): |
196
|
|
|
''' |
197
|
|
|
Returns calculated vertex scaleing for textures by textureID |
198
|
|
|
This nomalizes all subtextures to the height of the tallest texture. |
199
|
|
|
This is done because the vertex data sent to the gpu is the same for |
200
|
|
|
each |
201
|
|
|
''' |
202
|
|
|
|
203
|
|
|
tex = self.textures[texID] |
204
|
|
|
|
205
|
|
|
imgWidth = tex['width'] |
206
|
|
|
imgHeight = tex['height'] |
207
|
|
|
|
208
|
|
|
vertScaleY = imgHeight / float(self.maxSubTextureHeight) |
209
|
|
|
vertScaleX = imgWidth / float(self.maxSubTextureHeight) |
210
|
|
|
|
211
|
|
|
return (vertScaleX, vertScaleY) |
212
|
|
|
|
213
|
|
|
def gen_atlas(self): |
214
|
|
|
|
215
|
|
|
self.cursorPosY += self.lineHeight |
216
|
|
|
|
217
|
|
|
self.width = max(self.width, self.cursorPosX) |
218
|
|
|
self.height = self.cursorPosY |
219
|
|
|
|
220
|
|
|
self.maxSubTextureHeight = max(self.maxSubTextureHeight, self.lineHeight - 1) |
221
|
|
|
|
222
|
|
|
self.load_gl() |
223
|
|
|
|
224
|
|
|
# add data to blank gl texture with |
225
|
|
|
# glTexSubImage2D here |
226
|
|
|
for tex in self.textures: |
227
|
|
|
x1 = tex['x1'] |
228
|
|
|
y1 = tex['y1'] |
229
|
|
|
width = tex['width'] |
230
|
|
|
height = tex['height'] |
231
|
|
|
texData = tex['texData'] |
232
|
|
|
|
233
|
|
|
pgl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, x1, y1, width, height, self.texFormat, gl.GL_UNSIGNED_BYTE, texData) |
234
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.