This repository was archived by the owner on Jan 8, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathNTFT.py
305 lines (259 loc) · 7.28 KB
/
NTFT.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#NTFT.py by pbsds
#AGPL3 licensed
#
#PIL is required to read and write images to disk
#
#Credits:
#
# -The guys behind TiledGGD. This sped up my work a lit.
# -Jsafive for supplying .ugo files
#
import sys, os, numpy as np
try:
import Image
hasPIL = True
except ImportError:
hasPIL = False
#helpers
def AscDec(ascii, LittleEndian=False):#Converts a ascii string into a decimal
ret = 0
l = map(ord, ascii)
if LittleEndian: l.reverse()
for i in l:
ret = (ret<<8) | i
return ret
def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an ascii string of chosen length
out = []
while dec <> 0:
out.insert(0, dec&0xFF)
dec >>= 8
#"".join(map(chr, out))
if length:
if len(out) > length:
#return "".join(map(chr, out[-length:]))
out = out[-length:]
if len(out) < length:
#return "".join(map(chr, [0]*(length-len(out)) + out))
out = [0]*(length-len(out)) + out
if LittleEndian: out.reverse()
return "".join(map(chr, out))
def clamp(value, min, max):
if value > max: return max
if value < min: return min
return value
#Class NTFT:
#
# The NTFT image format stores RGB values as 5 bits each: a value between 0 and 31.
# It has 1 bit of transparancy, which means its either vidssible or invisible. No gradients.
#
# How to use:
#
# Converting to NTFT file:
#
# image = ReadImage(input_path)
# NTFT().SetImage(image).WriteFile(output_path)
#
# Reading NTFT file:
#
# ntft = NTFT().ReadFile(input_path, (width, height))
# WriteImage(ntft.Image, output_path)
#
class NTFT:
def __init__(self):
self.Loaded = False
def ReadFile(self, path, size):
f = open(path, "rb")
ret = self.Read(f.read(), size)
f.close()
return ret
def Read(self, data, (w, h)):
#the actual stored data is a image with the sizes padded to the nearest power of 2. The image is then clipped out from it.
psize = []
for i in (w, h):
p = 1
while 1<<p < i:
p += 1
psize.append(1<<p)
pw, ph = psize
#check if it fits the file:
if pw*ph*2 <> len(data):
print "Invalid sizes"
return False
#JUST DO IT!
#self.Image = [[None for _ in xrange(h)] for _ in xrange(w)]
self.Image = np.zeros((w, h), dtype=">u4")
for y in xrange(h):
for x in xrange(w):
pos = (x + y*pw)*2
byte = AscDec(data[pos:pos+2], True)
#ARGB1555 -> RGBA8:
a = (byte >> 15 ) * 0xFF
b = (byte >> 10 & 0x1F) * 0xFF / 0x1F
g = (byte >> 5 & 0x1F) * 0xFF / 0x1F
r = (byte & 0x1F) * 0xFF / 0x1F
#self.Image[x][y] = (r<<24) | (g<<16) | (b<<8) | a#RGBA8
self.Image[x, y] = (r<<24) | (g<<16) | (b<<8) | a#RGBA8
self.Loaded = True
return self
def WriteFile(self, path):
if self.Loaded:
f = open(path, "wb")
f.write(self.Pack())
f.close()
return True
else:
return False
def Pack(self):
if not self.Loaded:
return False
#h = len(self.Image[0])
#w = len(self.Image)
w, h = self.Image.shape
#the actual stored data is a image with the sizes padded to the nearest power of 2
psize = []
for i in (w, h):
p = 1
while 1<<p < i:
p += 1
psize.append(1<<p)
out = []
for y in xrange(psize[1]):
for x in xrange(psize[0]):
#read
#c = self.Image[clamp(x, 0, w-1)][clamp(y, 0, h-1)]
c = self.Image[clamp(x, 0, w-1), clamp(y, 0, h-1)]
r = c >> 24
g = (c >> 16) & 0xFF
b = (c >> 8 ) & 0xFF
a = c & 0xFF
#convert
a = 1 if a >= 0x80 else 0
r = r * 0x1F / 0xFF
g = g * 0x1F / 0xFF
b = b * 0x1F / 0xFF
#store
out.append(DecAsc((a<<15) | (b<<10) | (g<<5) | r, 2, True))
return "".join(out)
def SetImage(self, Image):
self.Image = Image
self.Loaded = True
return self
#Function WriteImage:
#
# Writes a 2D array of uint32 RGBA values as a image file.
# Designed to work with NTFT.Image
#
# This function requires the PIl imaging module
def WriteImage(image, outputPath):
if not hasPIL:
print "Error: PIL not found!"
return False
#if not image: return False
out = image.tostring("F")
# out = []
# for y in xrange(len(image[0])):
# for x in xrange(len(image)):
# out.append(DecAsc(image[x][y], 4))
out = Image.fromstring("RGBA", (len(image), len(image[0])), out)
filetype = outputPath[outputPath.rfind(".")+1:]
out.save(outputPath, filetype)
return True
#Function ReadImage:
#
# Returns a 2D list of uint32 RGBA values of the image file.
# This can be passed into NTFT().SetImage()
#
# This function requires the PIl imaging module
def ReadImage(path):#TODO: make it support numpy
if not hasPIL: return False
image = Image.open(path)
pixeldata = image.getdata()
w, h = image.size
if len(pixeldata[0]) < 4:
def Combine((r, g, b)):
return (r << 24) | (g << 16) | (b << 8) | 0xFF
else:
def Combine((r, g, b, a)):
return (r << 24) | (g << 16) | (b << 8) | a
#ret = []
ret = np.zeros((w, h), dtype=">u4")
for x in xrange(w):
#line = []
for y in xrange(h):
ret[x, y] = Combine(pixeldata[y*w + x])#maybe make a more numpy efficient way?
#line.append(Combine(pixeldata[y*w + x]))
#ret.append(line)
return ret
#testing:
# i = NTFT().ReadFile("NTFTtests/kaeru.ntft", (36, 30))
# WriteImage(i.Image, "NTFTtests/kaeru.png")
# i = NTFT().ReadFile("NTFTtests/News.ntft", (32, 32))
# WriteImage(i.Image, "NTFTtests/News.png")
# i = NTFT().ReadFile("NTFTtests/Special Room.ntft", (32, 32))
# WriteImage(i.Image, "NTFTtests/Special Room.png")
#i = NTFT()
#i.Loaded = True
#i.Image = ReadImage("NTFTtests/geh.png")
#i.WriteFile("NTFTtests/geh.ntft")
if __name__ == "__main__":
print " == NTFT.py =="
print " == by pbsds =="
print " == v0.95 =="
print
if not hasPIL:
print "PIL not found! Exiting..."
sys.exit()
if len(sys.argv) < 2:
print "Usage:"
print " NTFT.py <input> [<output> [<width> <height>]]"
print ""
print "Can convert a NTFT to PNG or the other way around."
print "if <output> isn't specified it will be set to <input> with an another extension"
print ""
print "The NTFT file contain only the colordata, so it's up to the user to find or"
print "store the resolution of the image. <width> and <height> is required"
print "to convert a NTFT file to a image."
print "32x32 is the normal resolution for button icons in UGO files."
sys.exit()
input = sys.argv[1]
if input[-4:].lower() == "ntft" or len(sys.argv) >= 5:
print "Mode: NTFT -> image"
Encode = False
else:
print "Mode: image -> NTFT"
Encode = True#if false it'll decode
if len(sys.argv) >= 3:
output = sys.argv[2]
width, height = None, None
if len(sys.argv) >= 5:
if (not sys.argv[3].isdigit()) or (not sys.argv[4].isdigit()):
print "Invalid size input!"
sys.exit()
width = int(sys.argv[3])
height = int(sys.argv[4])
if not (width and height) and not Encode:
print "Image size not provided!"
sys.exit()
else:
output = ".".join(input.split(".")[:-1]) + (".ntft" if Encode else ".png")
print "Converting..."
if Encode:
try:
image = ReadImage(input)
except IOError as err:
print err
sys.exit()
i = NTFT()
i.Loaded = True
i.Image = image
i.WriteFile(output)
else:
try:
ntft = NTFT().ReadFile(input, (width, height))
except IOError as err:
print err
sys.exit()
if not ntft:#eeror message already printed
sys.exit()
WriteImage(ntft.Image, output)
print "Done!"