summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pix.py8
-rw-r--r--renderer.py152
-rw-r--r--requirements.txt5
-rw-r--r--transition.py67
-rw-r--r--window.py37
5 files changed, 182 insertions, 87 deletions
diff --git a/pix.py b/pix.py
index 41d28d6..1e00fcf 100644
--- a/pix.py
+++ b/pix.py
@@ -4,17 +4,9 @@ from threading import Thread
from window import PixDisplay
from immich import ImmichConnector
-def load_textures(pd):
- # Load two images for transition
- tex = ImageTexture("image1.jpg")
- pd.textures.append(tex)
- tex = ImageTexture("image2.jpg")
- pd.textures.append(tex)
-
if __name__ == "__main__":
immichConnector = ImmichConnector("http://192.168.1.13", "m5nqOoBc4uhAba21gZdCP3z8D3JT4GPxDXL2psd52EA")
pd = PixDisplay()
- #t1 = Thread(target=load_textures, daemon=True, args=(pd,))
t1 = Thread(target=immichConnector.idle, daemon=True, args=(pd,))
t1.start()
pd.main()
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
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..7d9001a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+flask
+numpy
+pillow
+pyopengl
+requests
diff --git a/transition.py b/transition.py
deleted file mode 100644
index a076889..0000000
--- a/transition.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from OpenGL.GL import *
-from OpenGL.GLUT import *
-from OpenGL.GLU import *
-
-class Transition:
- def draw(self, texture_prev, texture_next, window_width, window_height, 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.draw_image(texture_prev, window_width, window_height, 1 - alpha) # TODO instead of decreasing alpha, draw transparent letterboxes
- # Draw the second image (with transparency)
- self.draw_image(texture_next, window_width, window_height, alpha)
-
- return alpha >= 1.0 # Complete
-
-
-
- # Draw the image with blending enabled (to allow fade effect)
- def draw_image(self, texture, window_width, window_height, alpha):
- if not alpha: return
-
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glBindTexture(GL_TEXTURE_2D, texture.id)
- glColor4f(1.0, 1.0, 1.0, alpha) # Set alpha to control transparency
-
- # Calculate aspect ratio
- img_aspect = texture.width / float(texture.height)
- win_aspect = window_width / float(window_height)
-
- scaled_width = window_width
- scaled_height = window_height
-
- # Scale the image to fit inside the window while maintaining aspect ratio
- if img_aspect > win_aspect:
- # Image is wider than window, letterbox vertically
- scaled_height = window_width / img_aspect
- else:
- # Image is taller than window, letterbox horizontally
- scaled_width = window_height * img_aspect
-
- # Position the image so it is centered
- offset_x = (window_width - scaled_width) / 2
- offset_y = (window_height - scaled_height) / 2
-
- # Normalize coordinates to range from -1 to 1
- x1 = (offset_x / window_width ) * 2 - 1
- y1 = (offset_y / window_height) * 2 - 1
- x2 = ((offset_x + scaled_width ) / window_width ) * 2 - 1
- y2 = ((offset_y + scaled_height) / window_height) * 2 - 1
-
- # Draw the image in the center with scaling
- glBegin(GL_QUADS)
- glTexCoord2f(0, 0)
- glVertex2f(x1, y1)
- glTexCoord2f(1, 0)
- glVertex2f(x2, y1)
- glTexCoord2f(1, 1)
- glVertex2f(x2, y2)
- glTexCoord2f(0, 1)
- glVertex2f(x1, y2)
- glEnd()
-
- glDisable(GL_BLEND)
diff --git a/window.py b/window.py
index 29d0522..4c51bd1 100644
--- a/window.py
+++ b/window.py
@@ -3,7 +3,7 @@ from OpenGL.GLUT import *
from OpenGL.GLU import *
from time import time
-from transition import Transition
+from renderer import ImageRenderer, TransitionMix
from texture import ImageTexture
@@ -14,9 +14,12 @@ class PixDisplay:
self.image_time = 0
self.textures = []
self.current_texture_index = 0
- self.transition = Transition()
+ self.renderer = None
self.window_width = 0
self.window_height = 0
+ # TODO
+ self.max_framerate = 60
+ self.frame_time = int(1000 / self.max_framerate) # In ms
self.display_duration = 2.0
self.transition_duration = 0.5
@@ -39,9 +42,9 @@ class PixDisplay:
self.last_time = current_time
if not self.textures:
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- glLoadIdentity()
glClearColor(0.0, 0.0, 0.0, 1.0) # Set the background color to black
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+ #glLoadIdentity()
glutSwapBuffers()
return
@@ -60,25 +63,24 @@ class PixDisplay:
if window_width != self.window_width or window_height != self.window_height:
self.window_width, self.window_height = window_width, window_height
+ glClearColor(0.0, 0.0, 0.0, 1.0) # Set the background color to black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- glLoadIdentity()
+ #glLoadIdentity()
- self.transition.draw_image(self.texture_current, self.window_width, self.window_height, 1)
+ self.renderer.draw_static(self.texture_current, self.window_width, self.window_height, 1)
- glClearColor(0.0, 0.0, 0.0, 1.0) # Set the background color to black
glutSwapBuffers()
return
self.window_width, self.window_height = window_width, window_height
+ glClearColor(0.0, 0.0, 0.0, 1.0) # Set the background color to black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- glLoadIdentity()
+ #glLoadIdentity()
# DRAW
transition_time = self.image_time - self.display_duration
- complete = self.transition.draw(self.texture_current, self.texture_next, self.window_width, self.window_height, delta_time, transition_time, self.transition_duration)
-
- glClearColor(0.0, 0.0, 0.0, 1.0) # Set the background color to black
+ complete = self.renderer.draw_transition(self.texture_current, self.texture_next, self.window_width, self.window_height, delta_time, transition_time, self.transition_duration)
glutSwapBuffers()
@@ -86,6 +88,10 @@ class PixDisplay:
self.image_time = 0
self.current_texture_index = self.next_texture_index
+ def timer(self, value):
+ glutPostRedisplay()
+ glutTimerFunc(self.frame_time, self.timer, 0) # Schedule next frame
+
# Initialization and main loop
def main(self):
# Initialize the window
@@ -94,6 +100,8 @@ class PixDisplay:
glutCreateWindow("Image Viewer with Fade Transition")
glEnable(GL_TEXTURE_2D)
+ self.renderer = ImageRenderer()
+ self.renderer.set_transition(TransitionMix)
self.image_time = 0
self.start_time = time()
self.last_time = time()
@@ -104,7 +112,12 @@ class PixDisplay:
glOrtho(-1, 1, -1, 1, -1, 1)
glMatrixMode(GL_MODELVIEW)
+ # Enable alpha blending
+ glEnable(GL_BLEND)
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+
+ # Run display
glutDisplayFunc(self.display)
- glutIdleFunc(self.display)
+ glutTimerFunc(self.frame_time, self.timer, 0)
glutMainLoop()