Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EncodeTheCode/ce420165e40a8174daa6e248561a22f5 to your computer and use it in GitHub Desktop.
Save EncodeTheCode/ce420165e40a8174daa6e248561a22f5 to your computer and use it in GitHub Desktop.
import time
import png
from apng import APNG
from OpenGL.GL import *
import glfw
class APNGSprite:
def __init__(self, path, loop=True, frame_interval_ms=100):
apng = APNG.open(path)
self.frames = [(frame.to_bytes(), control) for frame, control in apng.frames]
self.count = len(self.frames)
if self.count == 0:
raise RuntimeError(f"No frames in '{path}'")
self.loop = loop
self.interval = max(frame_interval_ms, 1) / 1000.0
self.start_time = time.perf_counter()
self.active = True
data0, ctrl0 = self.frames[0]
w0, h0, rows, _ = png.Reader(bytes=data0).asRGBA8()
self.orig_w, self.orig_h = w0, h0
self.canvas = bytearray(w0 * h0 * 4)
self.prev_canvas = None
self.tex_id = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
for p in (GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER):
glTexParameteri(GL_TEXTURE_2D, p, GL_LINEAR)
for p in (GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T):
glTexParameteri(GL_TEXTURE_2D, p, GL_CLAMP_TO_EDGE)
def _compose_frame(self, idx):
data, ctrl = self.frames[idx]
w, h, rows, _ = png.Reader(bytes=data).asRGBA8()
frame_buf = b"".join(rows)
x0, y0 = ctrl.x_offset, ctrl.y_offset
if ctrl.depose_op == 1:
for yy in range(h):
off = ((y0+yy)*self.orig_w + x0) * 4
self.canvas[off:off + w*4] = b"\x00" * (w*4)
for yy in range(h):
base = ((y0+yy)*self.orig_w + x0)*4
row = frame_buf[yy*w*4:(yy+1)*w*4]
for px in range(w):
si, di = px*4, base + px*4
sr, sg, sb, sa = row[si:si+4]
dr, dg, db, da = self.canvas[di:di+4]
a = sa / 255.0
nr = int(sr*a + dr*(1-a))
ng = int(sg*a + dg*(1-a))
nb = int(sb*a + db*(1-a))
na = int(255*(a + da/255.0*(1-a)))
self.canvas[di:di+4] = bytes((nr,ng,nb,na))
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.orig_w, self.orig_h,
0, GL_RGBA, GL_UNSIGNED_BYTE, self.canvas)
if ctrl.depose_op == 2 and self.prev_canvas is not None:
self.canvas[:] = self.prev_canvas
else:
self.prev_canvas = bytes(self.canvas)
def draw(self, x, y, w=None, h=None):
if not self.active:
return
elapsed = time.perf_counter() - self.start_time
frame_idx = int(elapsed / self.interval)
if self.loop:
frame_idx %= self.count
else:
frame_idx = min(frame_idx, self.count - 1)
self._compose_frame(frame_idx)
draw_w = w if w is not None else self.orig_w
draw_h = h if h is not None else self.orig_h
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glBegin(GL_QUADS)
glTexCoord2f(0,1); glVertex2f(x, y)
glTexCoord2f(1,1); glVertex2f(x+draw_w, y)
glTexCoord2f(1,0); glVertex2f(x+draw_w, y+draw_h)
glTexCoord2f(0,0); glVertex2f(x, y+draw_h)
glEnd()
def delete(self):
if self.active:
glDeleteTextures([self.tex_id])
self.active = False
def __del__(self):
try: self.delete()
except: pass
def main():
if not glfw.init():
raise RuntimeError("Failed to init GLFW")
glfw.window_hint(glfw.FLOATING, glfw.TRUE)
window = glfw.create_window(640, 480, "APNG HUD Example", None, None)
if not window:
glfw.terminate()
raise RuntimeError("Failed to create window")
glfw.make_context_current(window)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glDisable(GL_DEPTH_TEST)
sprite = APNGSprite("nyan_cat.png", loop=True, frame_interval_ms=75)
last_time = time.perf_counter()
while not glfw.window_should_close(window):
current_time = time.perf_counter()
glfw.poll_events()
glClearColor(0.1,0.1,0.1,1)
glClear(GL_COLOR_BUFFER_BIT)
w, h = glfw.get_framebuffer_size(window)
glMatrixMode(GL_PROJECTION); glLoadIdentity()
glOrtho(0, w, 0, h, -1, 1)
glMatrixMode(GL_MODELVIEW); glLoadIdentity()
sprite.draw(10, h-10-sprite.orig_h)
glfw.swap_buffers(window)
# Sleep to avoid CPU hogging but keep animating
elapsed = current_time - last_time
if elapsed < sprite.interval:
time.sleep(sprite.interval - elapsed)
last_time = current_time
sprite.delete()
glfw.terminate()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment