-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSpriteSheet.pyx
290 lines (235 loc) · 10.8 KB
/
SpriteSheet.pyx
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
###cython: boundscheck=False, wraparound=False, nonecheck=False, cdivision=True, optimize.use_switch=True
# NUMPY IS REQUIRED
try:
import numpy
from numpy import ndarray, zeros, empty, uint8, int32, float64, float32, dstack, full, ones,\
asarray, ascontiguousarray
except ImportError:
print("\n<numpy> library is missing on your system."
"\nTry: \n C:\\pip install numpy on a window command prompt.")
raise SystemExit
cimport numpy as np
# CYTHON IS REQUIRED
try:
cimport cython
from cython.parallel cimport prange
from cpython cimport PyObject, PyObject_HasAttr, PyObject_IsInstance
from cpython.list cimport PyList_Append, PyList_GetItem, PyList_Size
except ImportError:
print("\n<cython> library is missing on your system."
"\nTry: \n C:\\pip install cython on a window command prompt.")
raise SystemExit
# PYGAME IS REQUIRED
try:
import pygame
from pygame import Color, Surface, SRCALPHA, RLEACCEL, BufferProxy
from pygame.surfarray import pixels3d, array_alpha, pixels_alpha, array3d, make_surface, blit_array
from pygame.image import frombuffer
except ImportError:
print("\n<Pygame> library is missing on your system."
"\nTry: \n C:\\pip install pygame on a window command prompt.")
raise SystemExit
# TODO CYTHON
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True)
def sprite_sheet_per_pixel(file_: str, chunk: int, rows_: int, columns_: int) -> list:
surface = pygame.image.load(file_)
buffer_ = surface.get_view('2')
w, h = surface.get_size()
source_array = numpy.frombuffer(buffer_, dtype=numpy.uint8).reshape((h, w, 4))
animation = []
for rows in range(rows_):
for columns in range(columns_):
array1 = source_array[rows * chunk:(rows + 1) * chunk,
columns * chunk:(columns + 1) * chunk, :]
surface_ = pygame.image.frombuffer(array1.copy(order='C'),
(tuple(array1.shape[:2])), 'RGBA')
animation.append(surface_.convert_alpha())
return animation
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True)
cpdef sprite_sheet_fs8(str file, int chunk, int rows_, int columns_, bint tweak_ = False, args=None):
"""
LOAD ANIMATION (SPRITE SHEET) INTO A PYTHON LIST CONTAINING SURFACES.
This version is slightly slower than sprite_sheet_fs8_numpy using Numpy arrays.
:param file : string; file to be split into sub-surface
:param chunk : Size of a sub-surface, e.g for a spritesheet containing 256x256 pixels surface use chunk = 256
:param rows_ : integer; number of rows (or number of surface embedded vertically)
:param columns_: integer; Number of columns (or number of horizontal embedded surface)
:param tweak_ : bool; If True allow to tweak the chunk size for asymetric sub-surface.
You must provide a tuple after tweak=True such args=(256,128).
args contains the width and height of the sub-surface.
:param args: : tuple; default value is None. Use args in conjunction to tweak o specify asymetric sub-surface.
e.g arg=(256, 128) for loading sub-surface 256x128
:return: a list of sub-surface
"""
cdef:
unsigned char [:, :, :] array = pixels3d(pygame.image.load(file))
cdef:
list animation = []
int rows, columns
int chunkx, chunky
int start_x, end_x, start_y, end_y;
int w, h
make_surface = pygame.pixelcopy.make_surface
if tweak_:
if args is not None:
if PyObject_IsInstance(args, (tuple, list)):
chunkx = args[0]
chunky = args[1]
else:
raise ValueError('\nArgument args must be a tuple or a list got %s ' % type(args))
else:
raise ValueError('\nArgument tweak=True must be followed by args=(value, value) ')
else:
chunkx = chunky = chunk
cdef:
unsigned char [:, :, ::1] empty_array = empty((chunkx, chunky, 3), uint8)
w = chunkx
h = chunky
if tweak_:
for rows in range(rows_):
start_y = rows * chunky
end_y = (rows + 1) * chunky
for columns in range(columns_):
start_x = columns * chunkx
end_x = (columns + 1) * chunkx
array1 = splitx(array, start_x, start_y, w, h, empty_array)
sub_surface = make_surface(numpy.asarray(array1))
# sub_surface.set_colorkey((0, 0, 0, 0), RLEACCEL)
PyList_Append(animation, sub_surface)
else:
for rows in range(rows_):
start_y = rows * chunky
end_y = (rows + 1) * chunky
for columns in range(columns_):
start_x = columns * chunkx
end_x = (columns + 1) * chunkx
array1 = splitx(array, start_x, start_y, w, h, empty_array)
sub_surface = make_surface(numpy.asarray(array1))
# sub_surface.set_colorkey((0, 0, 0, 0), RLEACCEL)
PyList_Append(animation, sub_surface)
return animation
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True)
cdef inline unsigned char [:, :, :] splitx(
unsigned char [:, :, :] array_,
int start_x, int start_y, int w, int h,
unsigned char [:, :, :] block) nogil:
"""
SPLIT AN ARRAY INTO BLOCK OF EQUAL SIZES
:param array_ : unsigned char; array of size w x h x 3 to parse into sub blocks
:param start_x: int; start of the block (x value)
:param start_y: int; start of the block (y value)
:param w : int; width of the block
:param h : int; height of the block
:param block : unsigned char; empty block of size w_n x h_n x 3 to fill up
:return : Return 3d array of size (w_n x h_n x 3) of RGB pixels
"""
cdef:
int x, y, xx, yy
for x in prange(w):
xx = start_x + x
for y in range(h):
yy = start_y + y
block[x, y, 0] = array_[xx, yy, 0]
block[x, y, 1] = array_[xx, yy, 1]
block[x, y, 2] = array_[xx, yy, 2]
return block
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
cpdef sprite_sheet_fs8_numpy(str file, int chunk, int columns_,
int rows_, tweak_= False, args=None):
"""
RETRIEVE ALL SPRITES FROM A SPRITE SHEETS.
Method using numpy arrays.
:param file : str, full path to the texture
:param chunk : int, size of a single CREDIT_SPRITE in bytes e.g 64x64 (equal
:param rows_ : int, number of rows
:param columns_: int, number of column
:param tweak_ : bool, modify the chunk sizes (in bytes) in order to process
data with non equal width and height e.g 320x200
:param args : tuple, used with tweak_, args is a tuple containing the new chunk size,
e.g (320, 200)
:return: list, Return textures (surface) containing per-pixel transparency into a
python list
"""
assert PyObject_IsInstance(file, str), \
'Expecting string for argument file got %s: ' % type(file)
assert PyObject_IsInstance(chunk, int),\
'Expecting int for argument number got %s: ' % type(chunk)
assert PyObject_IsInstance(rows_, int) and PyObject_IsInstance(columns_, int), \
'Expecting int for argument rows_ and columns_ ' \
'got %s, %s ' % (type(rows_), type(columns_))
cdef int width, height
try:
image_ = pygame.image.load(file)
width, height = image_.get_size()
except (pygame.error, ValueError):
raise FileNotFoundError('\nFile %s is not found ' % file)
if width==0 or height==0:
raise ValueError(
'Surface dimensions is not correct, must be: (w>0, h>0) got (w:%s, h:%s) ' % (width, height))
try:
# Reference pixels into a 3d array
# pixels3d(Surface) -> array
# Create a new 3D array that directly references the pixel values
# in a Surface. Any changes to the array will affect the pixels in
# the Surface. This is a fast operation since no data is copied.
# This will only work on Surfaces that have 24-bit or 32-bit formats.
# Lower pixel formats cannot be referenced.
rgb_array_ = pixels3d(image_)
except (pygame.error, ValueError):
# Copy pixels into a 3d array
# array3d(Surface) -> array
# Copy the pixels from a Surface into a 3D array.
# The bit depth of the surface will control the size of the integer values,
# and will work for any type of pixel format.
# This function will temporarily lock the Surface as
# pixels are copied (see the Surface.lock()
# lock the Surface memory for pixel access
# - lock the Surface memory for pixel access method).
try:
rgb_array_ = pygame.surfarray.array3d(image_)
except (pygame.error, ValueError):
raise ValueError('\nIncompatible pixel format.')
cdef:
np.ndarray[np.uint8_t, ndim=3] rgb_array = rgb_array_
np.ndarray[np.uint8_t, ndim=3] array1 = empty((chunk, chunk, 3), dtype=uint8)
int chunkx, chunky, rows = 0, columns = 0
# modify the chunk size
if tweak_ and args is not None:
if PyObject_IsInstance(args, tuple):
try:
chunkx = args[0]
chunky = args[1]
except IndexError:
raise IndexError('Parse argument not understood.')
if chunkx==0 or chunky==0:
raise ValueError('Chunkx and chunky cannot be equal to zero.')
if (width % chunkx) != 0:
raise ValueError('Chunkx size value is not a correct fraction of %s ' % width)
if (height % chunky) != 0:
raise ValueError('Chunky size value is not a correct fraction of %s ' % height)
else:
raise ValueError('Parse argument not understood.')
else:
chunkx, chunky = chunk, chunk
cdef:
list animation = []
make_surface = pygame.pixelcopy.make_surface
# split sprite-sheet into many sprites
for rows in range(rows_):
for columns in range(columns_):
array1 = rgb_array[columns * chunkx:(columns + 1) * chunkx, rows * chunky:(rows + 1) * chunky, :]
surface_ = make_surface(array1).convert()
surface_.set_colorkey((0, 0, 0, 0), RLEACCEL)
PyList_Append(animation, surface_)
return animation