From 45268cdfe4a8caa4e8f3a3a9232c8df6fbf63a1f Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Thu, 27 Nov 2025 20:06:01 +0100 Subject: [PATCH] update reaquirements. removed redundant loading logic --- pyPhotoAlbum/models.py | 68 +++++++++++++++--------------------- pyPhotoAlbum/pdf_exporter.py | 35 ++----------------- pyproject.toml | 1 + 3 files changed, 31 insertions(+), 73 deletions(-) diff --git a/pyPhotoAlbum/models.py b/pyPhotoAlbum/models.py index 5dbf135..4b70430 100644 --- a/pyPhotoAlbum/models.py +++ b/pyPhotoAlbum/models.py @@ -131,20 +131,38 @@ class ImageData(BaseLayoutElement): self._async_loading = False self._async_load_requested = False + def resolve_image_path(self) -> Optional[str]: + """ + Resolve the image path to an absolute path. + + Returns the absolute path if the image exists, None otherwise. + """ + if not self.image_path: + return None + + # Already absolute + if os.path.isabs(self.image_path): + if os.path.exists(self.image_path): + return self.image_path + return None + + # Relative path - look in project folder + project_folder, _ = get_asset_search_paths() + if project_folder: + full_path = os.path.join(project_folder, self.image_path) + if os.path.exists(full_path): + return full_path + + return None + def _extract_dimensions_metadata(self): """ Extract image dimensions without loading the full image. Uses PIL's lazy loading to just read the header. """ try: - # Resolve path - image_path = self.image_path - if not os.path.isabs(self.image_path): - project_folder, search_paths = get_asset_search_paths() - if project_folder and os.path.exists(os.path.join(project_folder, self.image_path)): - image_path = os.path.join(project_folder, self.image_path) - - if os.path.exists(image_path): + image_path = self.resolve_image_path() + if image_path: # Use PIL to just read dimensions (fast, doesn't load pixel data) with Image.open(image_path) as img: width, height = img.width, img.height @@ -166,44 +184,14 @@ class ImageData(BaseLayoutElement): """Render the image using OpenGL""" from OpenGL.GL import (glBegin, glEnd, glVertex2f, glColor3f, glColor4f, GL_QUADS, GL_LINE_LOOP, glEnable, glDisable, GL_TEXTURE_2D, glBindTexture, glTexCoord2f, - glGenTextures, glTexImage2D, GL_RGBA, GL_UNSIGNED_BYTE, - glTexParameteri, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_LINEAR, - glDeleteTextures) - from PIL import Image - import os + glTexParameteri, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_LINEAR) x, y = self.position w, h = self.size - - # Note: Rotation is now handled at the PIL image level, not visually - # The image data itself is rotated, so we render it without transformation - - # Try to load and render the actual image texture_id = None - # Handle both absolute and relative paths - image_full_path = self.image_path - if self.image_path and not os.path.isabs(self.image_path): - # Relative path - only look in project folder (assets) - # Search paths are only used for healing, not for rendering - project_folder, _ = get_asset_search_paths() - - if project_folder: - full_path = os.path.join(project_folder, self.image_path) - if os.path.exists(full_path): - image_full_path = full_path - else: - print(f"ImageData: Could not resolve path: {self.image_path}") - print(f" Expected at: {full_path}") - print(f" Image needs healing - not found in assets folder") - - # NOTE: Async loading is now handled by page_layout.py calling request_image_load() - # This sync path should only be reached if async loading is not available - # The actual image will be loaded in the background and the texture created - # via _on_async_image_loaded() callback when ready - # Create texture from pending image if one exists (deferred from async load) - # This ensures texture creation happens when GL context is active + # Texture creation must happen during render when GL context is active if hasattr(self, '_pending_pil_image') and self._pending_pil_image is not None: self._create_texture_from_pending_image() diff --git a/pyPhotoAlbum/pdf_exporter.py b/pyPhotoAlbum/pdf_exporter.py index 87e1831..c9757d6 100644 --- a/pyPhotoAlbum/pdf_exporter.py +++ b/pyPhotoAlbum/pdf_exporter.py @@ -409,37 +409,6 @@ class PDFExporter: self._render_element(params.canvas, params.element, params.x_offset_mm, params.page_width_pt, params.page_height_pt, params.page_number) - def _resolve_image_path(self, image_path: str) -> Optional[str]: - """ - Resolve an image path, handling both absolute and relative paths. - Uses the same logic as ImageData.render() for consistency. - - Args: - image_path: The image path (absolute or relative) - - Returns: - Resolved absolute path if found, None otherwise - """ - if not image_path: - return None - - # If already absolute and exists, return it - if os.path.isabs(image_path) and os.path.exists(image_path): - return image_path - - # For relative paths, only look in project folder (assets) - # Search paths are only used for healing, not for PDF export - from pyPhotoAlbum.models import get_asset_search_paths - - project_folder, _ = get_asset_search_paths() - - if project_folder: - full_path = os.path.join(project_folder, image_path) - if os.path.exists(full_path): - return full_path - - return None - def _render_image(self, ctx: RenderContext): """ Render an image element on the PDF canvas. @@ -447,8 +416,8 @@ class PDFExporter: Args: ctx: RenderContext containing all rendering parameters """ - # Resolve image path (handles both absolute and relative paths) - image_full_path = self._resolve_image_path(ctx.image_element.image_path) + # Resolve image path using ImageData's method + image_full_path = ctx.image_element.resolve_image_path() # Check if image exists if not image_full_path: diff --git a/pyproject.toml b/pyproject.toml index 6437ae7..6750b2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dev = [ "pytest-qt>=4.2.0", "pytest-cov>=4.0.0", "pytest-mock>=3.10.0", + "pdfplumber>=0.10.0", "flake8>=5.0.0", "black>=22.0.0", "mypy>=0.990",