summaryrefslogtreecommitdiff
path: root/renderer.py
diff options
context:
space:
mode:
authorTim Keller <tjkeller.xyz>2025-05-10 11:52:02 -0500
committerTim Keller <tjkeller.xyz>2025-05-10 11:52:02 -0500
commit2bd4da7678b027843cac1a03a1b5f6cc70a7cc81 (patch)
treeea6a10a1a4a7c6ba355982697918d6bc6a0d94a3 /renderer.py
parent568a87f44a674276e6e55f9302cc9e44a0929f71 (diff)
downloadimmich-frame-2bd4da7678b027843cac1a03a1b5f6cc70a7cc81.tar.xz
immich-frame-2bd4da7678b027843cac1a03a1b5f6cc70a7cc81.zip
requirements.txt added, significantly reduced cpu usage using timer func and vertex/fragment shader instead of drawing new quad etc every frame
Diffstat (limited to 'renderer.py')
-rw-r--r--renderer.py152
1 files changed, 152 insertions, 0 deletions
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