""" Image Primitives Library Basic image operations: dimensions, pixels, resize, crop, paste. """ import numpy as np import cv2 def prim_width(img): if isinstance(img, (list, tuple)): raise TypeError(f"image:width expects an image array, got {type(img).__name__} with {len(img)} elements") return img.shape[1] def prim_height(img): if isinstance(img, (list, tuple)): import sys print(f"DEBUG image:height got list: {img[:3]}... (types: {[type(x).__name__ for x in img[:3]]})", file=sys.stderr) raise TypeError(f"image:height expects an image array, got {type(img).__name__} with {len(img)} elements: {img}") return img.shape[0] def prim_make_image(w, h, color=None): """Create a new image filled with color (default black).""" if color is None: color = [0, 0, 0] img = np.zeros((h, w, 3), dtype=np.uint8) img[:] = color return img def prim_copy(img): return img.copy() def prim_pixel(img, x, y): """Get pixel color at (x, y) as [r, g, b].""" h, w = img.shape[:2] if 0 <= x < w and 0 <= y < h: return list(img[int(y), int(x)]) return [0, 0, 0] def prim_set_pixel(img, x, y, color): """Set pixel at (x, y) to color, returns modified image.""" result = img.copy() h, w = result.shape[:2] if 0 <= x < w and 0 <= y < h: result[int(y), int(x)] = color return result def prim_sample(img, x, y): """Bilinear sample at float coordinates, returns [r, g, b] as floats.""" h, w = img.shape[:2] x = max(0, min(w - 1.001, x)) y = max(0, min(h - 1.001, y)) x0, y0 = int(x), int(y) x1, y1 = min(x0 + 1, w - 1), min(y0 + 1, h - 1) fx, fy = x - x0, y - y0 c00 = img[y0, x0].astype(float) c10 = img[y0, x1].astype(float) c01 = img[y1, x0].astype(float) c11 = img[y1, x1].astype(float) top = c00 * (1 - fx) + c10 * fx bottom = c01 * (1 - fx) + c11 * fx return list(top * (1 - fy) + bottom * fy) def prim_channel(img, c): """Extract single channel (0=R, 1=G, 2=B).""" return img[:, :, c] def prim_merge_channels(r, g, b): """Merge three single-channel arrays into RGB image.""" return np.stack([r, g, b], axis=2).astype(np.uint8) def prim_resize(img, w, h, mode="linear"): """Resize image to w x h.""" interp = cv2.INTER_LINEAR if mode == "nearest": interp = cv2.INTER_NEAREST elif mode == "cubic": interp = cv2.INTER_CUBIC elif mode == "area": interp = cv2.INTER_AREA return cv2.resize(img, (int(w), int(h)), interpolation=interp) def prim_crop(img, x, y, w, h): """Crop rectangle from image.""" x, y, w, h = int(x), int(y), int(w), int(h) ih, iw = img.shape[:2] x = max(0, min(x, iw - 1)) y = max(0, min(y, ih - 1)) w = min(w, iw - x) h = min(h, ih - y) return img[y:y+h, x:x+w].copy() def prim_paste(dst, src, x, y): """Paste src onto dst at position (x, y).""" result = dst.copy() x, y = int(x), int(y) sh, sw = src.shape[:2] dh, dw = dst.shape[:2] # Clip to bounds sx1 = max(0, -x) sy1 = max(0, -y) dx1 = max(0, x) dy1 = max(0, y) sx2 = min(sw, dw - x) sy2 = min(sh, dh - y) if sx2 > sx1 and sy2 > sy1: result[dy1:dy1+(sy2-sy1), dx1:dx1+(sx2-sx1)] = src[sy1:sy2, sx1:sx2] return result PRIMITIVES = { # Dimensions 'width': prim_width, 'height': prim_height, # Creation 'make-image': prim_make_image, 'copy': prim_copy, # Pixel access 'pixel': prim_pixel, 'set-pixel': prim_set_pixel, 'sample': prim_sample, # Channels 'channel': prim_channel, 'merge-channels': prim_merge_channels, # Geometry 'resize': prim_resize, 'crop': prim_crop, 'paste': prim_paste, }