﻿from PIL import Image, ImageOps
import numpy as np
import time

def hue_np(rgb_u8: np.ndarray) -> np.ndarray:
    """
    Vectorized hue for RGB uint8 array shape (N, 3) or (W, 3).
    Returns hue in [0, 1).
    """
    rgb = rgb_u8.astype(np.float32) / 255.0
    r = rgb[..., 0]
    g = rgb[..., 1]
    b = rgb[..., 2]

    maxc = np.maximum(np.maximum(r, g), b)
    minc = np.minimum(np.minimum(r, g), b)
    delta = maxc - minc

    h = np.zeros_like(maxc)

    # Avoid division by zero
    nonzero = delta > 1e-12

    # Masks for which channel is max
    is_r = (maxc == r) & nonzero
    is_g = (maxc == g) & nonzero
    is_b = (maxc == b) & nonzero

    h[is_r] = ((g[is_r] - b[is_r]) / delta[is_r]) % 6.0
    h[is_g] = ((b[is_g] - r[is_g]) / delta[is_g]) + 2.0
    h[is_b] = ((r[is_b] - g[is_b]) / delta[is_b]) + 4.0

    h = (h / 6.0) % 1.0
    return h

def sort_image_lumY_hueX(input_path: str, output_path: str, progress: bool = True) -> None:
    t0 = time.time()

    # 1) Load with EXIF orientation fixed, preserve original W/H after that
    img = ImageOps.exif_transpose(Image.open(input_path)).convert("RGB")
    arr = np.array(img)  # (H, W, 3)
    h, w, _ = arr.shape

    if progress:
        print(f"[1/3] Loaded {w}x{h} ({w*h:,} px).")

    # 2) Global sort by luminance so TOP = bright, BOTTOM = dark
    pixels = arr.reshape(-1, 3)
    # Perceptual-ish luminance
    lum = 0.2126 * pixels[:, 0] + 0.7152 * pixels[:, 1] + 0.0722 * pixels[:, 2]
    order = np.argsort(-lum, kind="stable")  # minus => descending (bright first)
    sorted_pixels = pixels[order].astype(np.uint8)
    grid = sorted_pixels.reshape(h, w, 3)

    if progress:
        print(f"[2/3] Sorted globally by luminance (top bright).")

    # 3) For each row, sort by hue (left -> right)
    if progress:
        print(f"[3/3] Sorting rows by HUE...")
    last_print = time.time()

    for y in range(h):
        row = grid[y]  # (W, 3)
        keys = hue_np(row)  # (W,)
        idx = np.argsort(keys, kind="stable")
        grid[y] = row[idx]

        if progress:
            # update at most ~20x/sec so console doesn't melt
            now = time.time()
            if now - last_print > 0.05 or y == h - 1:
                pct = (y + 1) * 100.0 / h
                print(f"\r    {y+1}/{h} rows  ({pct:5.1f}%)", end="")
                last_print = now

    if progress:
        print()  # newline

    Image.fromarray(grid, mode="RGB").save(output_path)

    if progress:
        dt = time.time() - t0
        print(f"Done. Saved: {output_path}  |  time: {dt:.2f}s")

if __name__ == "__main__":
    sort_image_lumY_hueX("in.jpg", "out_lumY_hueX.png", progress=True)