diff options
| author | Tim Keller <tjk@tjkeller.xyz> | 2025-06-24 19:26:15 -0500 |
|---|---|---|
| committer | Tim Keller <tjk@tjkeller.xyz> | 2025-06-24 19:26:15 -0500 |
| commit | 4be4fd53b3f71d1da55fc7f4c31640bf6f1c4992 (patch) | |
| tree | 2a930728b4d5d81820211bd7790d9684f0a9f252 /renderer.py | |
| parent | ffa5ff333eabffe07394fb21bc413d7d238ee651 (diff) | |
| parent | 4c3d572eb850c32a45ec9cbaf82688d45c1eebf4 (diff) | |
| download | immich-frame-4be4fd53b3f71d1da55fc7f4c31640bf6f1c4992.tar.xz immich-frame-4be4fd53b3f71d1da55fc7f4c31640bf6f1c4992.zip | |
merge pixpy repo into this repo
Diffstat (limited to 'renderer.py')
| -rw-r--r-- | renderer.py | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/renderer.py b/renderer.py new file mode 100644 index 0000000..ae4be12 --- /dev/null +++ b/renderer.py @@ -0,0 +1,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) |
