Created
June 23, 2025 06:19
-
-
Save EncodeTheCode/6da348d6e068b8f461cfe2d0bcee977c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import heapq | |
import re | |
import numpy as np | |
from OpenGL.GL import * | |
from stb import image | |
# === Global registries & ID recycling heap === | |
_images = {} # auto-named images | |
_named_images = {} # user-named images | |
_next_id = 1 | |
_free_ids = [] | |
class GLImage: | |
def __init__(self, filepath, | |
x_pos=0, y_pos=0, | |
x=-1, y=-1, | |
scale=1.0, name=None): | |
global _images, _named_images | |
self.filepath = filepath | |
self.x_pos = x_pos | |
self.y_pos = y_pos | |
self.scale = scale | |
self.visible = True | |
self.texture_id = None | |
self.layer = 0 # default layer (z-index) | |
# size overrides; -1 means “use original” | |
self._override_x = x | |
self._override_y = y | |
# load texture, compute sizes | |
self._load_texture() | |
self._compute_display_size() | |
# assign name & register | |
if name is None: | |
self._is_named = False | |
self.name = self._generate_unique_name() | |
_images[self.name] = self | |
else: | |
if name in _named_images: | |
raise ValueError(f"A named image '{name}' already exists.") | |
self._is_named = True | |
self.name = name | |
_named_images[self.name] = self | |
# ——— auto-naming helpers ——— | |
@staticmethod | |
def _generate_unique_name(): | |
global _images, _next_id, _free_ids | |
while _free_ids: | |
candidate = heapq.heappop(_free_ids) | |
candidate_name = f"image{candidate:03d}" | |
if candidate_name not in _images: | |
return candidate_name | |
name = f"image{_next_id:03d}" | |
_next_id += 1 | |
return name | |
# ——— registry access ——— | |
@staticmethod | |
def get_image(name): | |
return _images.get(name) or _named_images.get(name) | |
@staticmethod | |
def all_images(auto_named=True, user_named=True): | |
imgs = [] | |
if auto_named: | |
imgs.extend(_images.values()) | |
if user_named: | |
imgs.extend(_named_images.values()) | |
return imgs | |
# ——— texture loading & sizing ——— | |
def _load_texture(self): | |
img = image.load(self.filepath) | |
if img is None: | |
raise RuntimeError(f"Failed to load '{self.filepath}'") | |
self._orig_w, self._orig_h, channels = img.width, img.height, img.channels | |
if channels == 3: | |
arr = np.frombuffer(img.pixels, dtype=np.uint8) | |
arr = arr.reshape((self._orig_h, self._orig_w, 3)) | |
alpha = 255 * np.ones((self._orig_h, self._orig_w, 1), np.uint8) | |
arr = np.concatenate((arr, alpha), axis=2) | |
pixels = arr.tobytes() | |
else: | |
pixels = img.pixels # channels == 4 | |
self.texture_id = glGenTextures(1) | |
glBindTexture(GL_TEXTURE_2D, self.texture_id) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, | |
self._orig_w, self._orig_h, 0, | |
GL_RGBA, GL_UNSIGNED_BYTE, pixels) | |
glBindTexture(GL_TEXTURE_2D, 0) | |
image.free(img) | |
def _compute_display_size(self): | |
base_w = self._override_x if self._override_x != -1 else self._orig_w | |
base_h = self._override_y if self._override_y != -1 else self._orig_h | |
self._disp_w = base_w * self.scale | |
self._disp_h = base_h * self.scale | |
# ——— public setters ——— | |
def set_position(self, x_pos, y_pos): | |
self.x_pos = x_pos | |
self.y_pos = y_pos | |
def set_scale(self, scale): | |
self.scale = scale | |
self._compute_display_size() | |
def set_size(self, x=-1, y=-1): | |
self._override_x = x | |
self._override_y = y | |
self._compute_display_size() | |
def show(self): | |
self.visible = True | |
def hide(self): | |
self.visible = False | |
# ——— deletion & ID recycling ——— | |
def delete(self): | |
global _images, _named_images, _free_ids, _next_id | |
if self.texture_id: | |
glDeleteTextures([self.texture_id]) | |
self.texture_id = None | |
if self._is_named: | |
_named_images.pop(self.name, None) | |
else: | |
_images.pop(self.name, None) | |
m = re.match(r'image(\d{3})$', self.name) | |
if m: | |
heapq.heappush(_free_ids, int(m.group(1))) | |
# shrink _next_id if highest freed ID matches | |
while _free_ids and max(_free_ids) == _next_id - 1: | |
top = max(_free_ids) | |
_free_ids.remove(top) | |
heapq.heapify(_free_ids) | |
_next_id -= 1 | |
def draw(self): | |
if not self.visible or self.texture_id is None: | |
return | |
glEnable(GL_TEXTURE_2D) | |
glBindTexture(GL_TEXTURE_2D, self.texture_id) | |
glEnable(GL_BLEND) | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) | |
x0, y0 = self.x_pos, self.y_pos | |
w, h = self._disp_w, self._disp_h | |
glBegin(GL_QUADS) | |
glTexCoord2f(0, 0); glVertex2f(x0, y0) | |
glTexCoord2f(1, 0); glVertex2f(x0 + w, y0) | |
glTexCoord2f(1, 1); glVertex2f(x0 + w, y0 + h) | |
glTexCoord2f(0, 1); glVertex2f(x0, y0 + h) | |
glEnd() | |
glBindTexture(GL_TEXTURE_2D, 0) | |
glDisable(GL_TEXTURE_2D) | |
glDisable(GL_BLEND) | |
# ——— helper functions ——— | |
def GLImage_Delete(name: str) -> bool: | |
"""Delete image by name (auto or user).""" | |
img = GLImage.get_image(name) | |
if img: | |
img.delete() | |
return True | |
return False | |
def GLImage_SetLayerOrder(name: str, layer: int) -> bool: | |
""" | |
Set the drawing layer (z-index) of an image by its name. | |
Higher layers draw on top. Returns True if found and set. | |
""" | |
img = GLImage.get_image(name) | |
if not img: | |
return False | |
img.layer = layer | |
return True | |
def GLImage_DrawAll(): | |
""" | |
Draw all registered images in ascending layer order. | |
Call this in your render loop instead of drawing individually. | |
""" | |
for img in sorted(GLImage.all_images(), key=lambda im: im.layer): | |
img.draw() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment