From 2bd4da7678b027843cac1a03a1b5f6cc70a7cc81 Mon Sep 17 00:00:00 2001 From: Tim Keller Date: Sat, 10 May 2025 11:52:02 -0500 Subject: requirements.txt added, significantly reduced cpu usage using timer func and vertex/fragment shader instead of drawing new quad etc every frame --- renderer.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 renderer.py (limited to 'renderer.py') diff --git a/renderer.py b/renderer.py new file mode 100644 index 0000000..92d0686 --- /dev/null +++ b/renderer.py @@ -0,0 +1,152 @@ +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._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): + self.transition = transition(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 + + def _init_shader(self): + 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 + + def _init_quad(self): + # 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 + self.vao = glGenVertexArrays(1) + vbo = glGenBuffers(1) + glBindVertexArray(self.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) + + def draw_static(self, tex, win_w, win_h, alpha): + glUseProgram(self.shader) + glBindVertexArray(self.vao) + 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_transition(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration): + assert self.transition, "No transition has been set" + return self.transition.draw(tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration) + + +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): + pass + + +class TransitionMix(Transition): + def draw(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration): + # Update alpha value for fade effect + alpha = transition_time / transition_duration + if alpha > 1.0: + alpha = 1.0 + + # Draw the first image + self.renderer.draw_static(tex_start, win_w, win_h, 1 - alpha) # TODO instead of decreasing alpha, draw transparent letterboxes + # Draw the second image (with transparency) + self.renderer.draw_static(tex_end, win_w, win_h, alpha) + + return alpha >= 1.0 # Complete -- cgit v1.2.3 From 3aed05a6cb265e4f60a17f87eb368fb33c93a562 Mon Sep 17 00:00:00 2001 From: Tim Keller Date: Sat, 10 May 2025 16:26:33 -0500 Subject: cleanup some --- renderer.py | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) (limited to 'renderer.py') diff --git a/renderer.py b/renderer.py index 92d0686..01c9f3f 100644 --- a/renderer.py +++ b/renderer.py @@ -8,7 +8,7 @@ class ImageRenderer: def __init__(self): # Setup shader and quad self.shader = self._init_shader() - self._init_quad() + self.vao = self._init_quad() # Get uniform locations from the shader self.uTransform = glGetUniformLocation(self.shader, "uTransform") @@ -21,8 +21,11 @@ class ImageRenderer: # Setup transition self.transition = None - def set_transition(self, transition): - self.transition = transition(self) + # State helper + self._state = None + + def set_transition(self, transition_cls): + self.transition = transition_cls(self) @staticmethod def compile_shader(source, shader_type): @@ -33,7 +36,8 @@ class ImageRenderer: raise RuntimeError(glGetShaderInfoLog(shader).decode()) # Raise error with log if failed return shader - def _init_shader(self): + @staticmethod + def _init_shader(): vertex_src = """ #version 330 core layout (location = 0) in vec2 aPos; // Vertex position @@ -68,7 +72,8 @@ class ImageRenderer: glLinkProgram(prog) return prog - def _init_quad(self): + @staticmethod + def _init_quad(): # Define a full-screen quad with positions and texture coordinates quad = np.array([ -1, -1, 0, 0, # Bottom-left @@ -78,9 +83,9 @@ class ImageRenderer: ], dtype=np.float32) # Create and bind a Vertex Array Object - self.vao = glGenVertexArrays(1) + vao = glGenVertexArrays(1) vbo = glGenBuffers(1) - glBindVertexArray(self.vao) + glBindVertexArray(vao) glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, quad.nbytes, quad, GL_STATIC_DRAW) @@ -91,8 +96,9 @@ class ImageRenderer: # 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_static(self, tex, win_w, win_h, alpha): + def draw_image(self, tex, win_w, win_h, alpha): glUseProgram(self.shader) glBindVertexArray(self.vao) glBindTexture(GL_TEXTURE_2D, tex.id) @@ -124,9 +130,25 @@ class ImageRenderer: # 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): assert self.transition, "No transition has been set" - return self.transition.draw(tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration) + + # 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) + + glutSwapBuffers() class Transition(Protocol): @@ -144,9 +166,6 @@ class TransitionMix(Transition): if alpha > 1.0: alpha = 1.0 - # Draw the first image - self.renderer.draw_static(tex_start, win_w, win_h, 1 - alpha) # TODO instead of decreasing alpha, draw transparent letterboxes - # Draw the second image (with transparency) - self.renderer.draw_static(tex_end, win_w, win_h, alpha) - - return alpha >= 1.0 # Complete + # 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) -- cgit v1.2.3 From 9a785dfddad4215672c41396b8554477c18b4066 Mon Sep 17 00:00:00 2001 From: Tim Keller Date: Sat, 10 May 2025 18:22:40 -0500 Subject: transition using arrow keys + skip function --- renderer.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'renderer.py') diff --git a/renderer.py b/renderer.py index 01c9f3f..909ad2b 100644 --- a/renderer.py +++ b/renderer.py @@ -21,9 +21,6 @@ class ImageRenderer: # Setup transition self.transition = None - # State helper - self._state = None - def set_transition(self, transition_cls): self.transition = transition_cls(self) @@ -139,14 +136,14 @@ class ImageRenderer: glutSwapBuffers() - def draw_transition(self, tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration): + 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) + self.transition.draw(tex_start, tex_end, win_w, win_h, delta_time, transition_time, transition_duration, reversed) glutSwapBuffers() @@ -155,12 +152,12 @@ 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): + 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): + 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: -- cgit v1.2.3 From 4c3d572eb850c32a45ec9cbaf82688d45c1eebf4 Mon Sep 17 00:00:00 2001 From: Tim Keller Date: Tue, 24 Jun 2025 19:16:36 -0500 Subject: add ability to change out albums list during runtime --- renderer.py | 1 + 1 file changed, 1 insertion(+) (limited to 'renderer.py') diff --git a/renderer.py b/renderer.py index 909ad2b..ae4be12 100644 --- a/renderer.py +++ b/renderer.py @@ -98,6 +98,7 @@ class ImageRenderer: 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 -- cgit v1.2.3