Always create power-of-two sized textures.
[bsnes:xml-shaders.git] / reference / texreader.py
1 #!/usr/bin/python
2 import re
3 import string
4 from OpenGL.GL import *
5
6 TOKEN_RE = re.compile("[^" + string.whitespace + "]+")
7 WS_RE = re.compile("[" + string.whitespace + "]+")
8
9
10 class TexReaderException(Exception):
11         pass
12
13
14 def next_pot(number):
15         """
16         Return the first power-of-two greater than a given number.
17
18         Assumes numbers will fit in 32-bit integers; most graphics cards have
19         maximum texture sizes vastly smaller than 2**32, so this should be fine.
20         """
21         number -= 1
22         number |= number >> 1
23         number |= number >> 2
24         number |= number >> 4
25         number |= number >> 8
26         number |= number >> 16
27         number += 1
28
29         return number
30
31
32 class Texture(object):
33
34         def __init__(self, width, height, pixels=None):
35                 self.textureID = glGenTextures(1)
36                 self.imageWidth = width
37                 self.imageHeight = height
38                 self.textureWidth = next_pot(width)
39                 self.textureHeight = next_pot(height)
40
41                 glBindTexture(GL_TEXTURE_2D, self.textureID)
42
43                 glTexImage2D(
44                                 GL_TEXTURE_2D, # target
45                                 0,             # mipmap level
46                                 GL_RGBA,       # internal format
47                                 self.textureWidth, self.textureHeight,
48                                 0,             # border size
49                                 GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
50                                 None,
51                         )
52
53                 if pixels:
54                         glTexSubImage2D(
55                                         GL_TEXTURE_2D, # target
56                                         0,             # mipmap level
57                                         0, 0,          # (xoffset, yoffset)
58                                         self.imageWidth, self.imageHeight,
59                                         GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
60                                         pixels,
61                                 )
62
63                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
64                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
65
66         def destroy(self):
67                 if self.textureID != None:
68                         glDeleteTextures([self.textureID])
69                         self.textureID = None
70                         self.imageWidth = None
71                         self.imageHeight = None
72                         self.textureWidth = None
73                         self.textureHeight = None
74
75         def __del__(self):
76                 self.destroy()
77
78
79 def parse_ppm(data):
80         """
81         Parse the PPM data from the given byte string.
82         """
83         try:
84                 magic, widthStr, heightStr, sampleMaxStr, pixelData = \
85                                 data.split(None, 4)
86         except ValueError:
87                 raise TexReaderException("Can't parse data: %r..." % (data[:40],))
88
89         if magic != "P6":
90                 raise TexReaderException("Unrecognised magic %r" % (magic,))
91
92         try:
93                 width = int(widthStr)
94                 height = int(heightStr)
95                 sampleMax = int(sampleMaxStr)
96         except ValueError, e:
97                 raise TexReaderException("Can't parse data: %s" % (str(e),))
98
99         if sampleMax != 255:
100                 raise TexReaderException("Textures must have 8 bits per channel.")
101
102         if len(pixelData) != (width * height * 3):
103                 raise TexReaderException("Got %d bytes of pixel data, expected %d"
104                                 % (len(pixelData), width * height * 3))
105
106         # Now to convert this packed RGB pixel data to RGBA data that OpenGL can
107         # understand.
108         pixels = []
109         for pxOffset in range(0, (width * height * 3), 3):
110                 pixels.append(pixelData[pxOffset:pxOffset+3])
111                 pixels.append("\xff")
112
113         return (width, height, "".join(pixels))
114
115
116 def textureFromFile(filename):
117         with open(filename, "rb") as handle:
118                 width, height, pixels = parse_ppm(handle.read())
119
120         return Texture(width, height, pixels)
121
122
123 if __name__ == "__main__":
124         import sys
125         with open(sys.argv[1]) as handle:
126                 data = handle.read()
127                 print parse_ppm(data)