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
|
import numpy as np
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from typing import Protocol
class ImageRenderer:
def __init__(self):
# Setup shader and quad
self.shader = self._init_shader()
self.vao = self._init_quad()
# Get uniform locations from the shader
self.uTransform = glGetUniformLocation(self.shader, "uTransform")
self.uAlpha = glGetUniformLocation(self.shader, "uAlpha")
# Use the shader program and set default texture unit to 0
glUseProgram(self.shader)
glUniform1i(glGetUniformLocation(self.shader, "uTexture"), 0)
# Setup transition
self.transition = None
def set_transition(self, transition_cls):
self.transition = transition_cls(self)
@staticmethod
def compile_shader(source, shader_type):
shader = glCreateShader(shader_type) # Create a new shader object
glShaderSource(shader, source) # Attach the shader source code
glCompileShader(shader) # Compile the shader
if not glGetShaderiv(shader, GL_COMPILE_STATUS): # Check if compilation succeeded
raise RuntimeError(glGetShaderInfoLog(shader).decode()) # Raise error with log if failed
return shader
@staticmethod
def _init_shader():
vertex_src = """
#version 330 core
layout (location = 0) in vec2 aPos; // Vertex position
layout (location = 1) in vec2 aTexCoord; // Texture coordinates
uniform mat4 uTransform; // Transformation matrix
out vec2 TexCoord; // Output texture coordinate
void main() {
gl_Position = uTransform * vec4(aPos, 0.0, 1.0); // Apply transformation
TexCoord = aTexCoord; // Pass tex coord to fragment shader
}
"""
fragment_src = """
#version 330 core
in vec2 TexCoord; // Interpolated texture coordinates
out vec4 FragColor; // Final fragment color
uniform sampler2D uTexture; // Texture sampler
uniform float uAlpha; // Global alpha for transparency
void main() {
vec4 texColor = texture(uTexture, TexCoord); // Sample texture
FragColor = vec4(texColor.rgb, texColor.a * uAlpha); // Apply alpha blending
}
"""
# Compile and link shaders into a program
vs = ImageRenderer.compile_shader(vertex_src, GL_VERTEX_SHADER)
fs = ImageRenderer.compile_shader(fragment_src, GL_FRAGMENT_SHADER)
prog = glCreateProgram()
glAttachShader(prog, vs)
glAttachShader(prog, fs)
glLinkProgram(prog)
return prog
@staticmethod
def _init_quad():
# Define a full-screen quad with positions and texture coordinates
quad = np.array([
-1, -1, 0, 0, # Bottom-left
1, -1, 1, 0, # Bottom-right
-1, 1, 0, 1, # Top-left
1, 1, 1, 1, # Top-right
], dtype=np.float32)
# Create and bind a Vertex Array Object
vao = glGenVertexArrays(1)
vbo = glGenBuffers(1)
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, quad.nbytes, quad, GL_STATIC_DRAW)
# Setup vertex attributes: position (location = 0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
# Setup vertex attributes: texture coordinates (location = 1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(8))
glEnableVertexAttribArray(1)
return vao
def draw_image(self, tex, win_w, win_h, alpha):
glUseProgram(self.shader)
glBindVertexArray(self.vao)
# FIXME check if tex.id is None
glBindTexture(GL_TEXTURE_2D, tex.id)
# Calculate aspect ratios
img_aspect = tex.width / tex.height
win_aspect = win_w / win_h
# Calculate scaling factors to preserve image aspect ratio
if img_aspect > win_aspect:
sx = 1.0
sy = win_aspect / img_aspect
else:
sx = img_aspect / win_aspect
sy = 1.0
# Create transformation matrix for aspect-ratio-correct rendering
transform = np.array([
[sx, 0, 0, 0],
[0, sy, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
], dtype=np.float32)
# Pass transformation and alpha to the shader
glUniformMatrix4fv(self.uTransform, 1, GL_FALSE, transform)
glUniform1f(self.uAlpha, alpha)
# Draw the textured quad
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
def draw_static(self, tex, win_w, win_h, alpha):
# Set the background color to black
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.draw_image(tex, win_w, win_h, alpha)
glutSwapBuffers()
def draw_transition(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration, reversed):
assert self.transition, "No transition has been set"
# Set the background color to black
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.transition.draw(tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration, reversed)
glutSwapBuffers()
class Transition(Protocol):
def __init__(self, renderer):
self.renderer = renderer
def draw(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration, reversed):
pass
class TransitionMix(Transition):
def draw(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration, reversed):
# Update alpha value for fade effect
alpha = transition_time / transition_duration
if alpha > 1.0:
alpha = 1.0
# Draw the images on top of one another
self.renderer.draw_image(tex_start, win_w, win_h, 1 - alpha) # TODO instead of decreasing alpha, draw transparent letterboxes
self.renderer.draw_image(tex_end, win_w, win_h, alpha)
|