File size: 1,863 Bytes
1e3ce4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""Artifacts: the pixel-space data structures that only PixelCursor may produce.

External code may hold and inspect Artifacts but cannot construct them directly;
the only path to a fresh Artifact is a cursor write_* op or a loader in this
module. This is the structural enforcement of the "cursor owns the studio" claim.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Union
from pathlib import Path

import numpy as np

from ._internal import _MUTATION_TOKEN, _verify


@dataclass(frozen=True, slots=True, eq=False, repr=False)
class Image:
    pixels: np.ndarray
    name: str = ""
    _token: object = field(default=None, repr=False)

    def __post_init__(self) -> None:
        _verify(self._token)

    def __repr__(self) -> str:
        return f"Image(shape={tuple(self.pixels.shape)}, name={self.name!r})"


@dataclass(frozen=True, slots=True, eq=False, repr=False)
class FrameStack:
    frames: np.ndarray
    fps: int = 24
    name: str = ""
    _token: object = field(default=None, repr=False)

    def __post_init__(self) -> None:
        _verify(self._token)

    @property
    def num_frames(self) -> int:
        return int(self.frames.shape[0])

    def __repr__(self) -> str:
        return f"FrameStack(shape={tuple(self.frames.shape)}, fps={self.fps}, name={self.name!r})"


Artifact = Union[Image, FrameStack]


def _new_image(pixels: np.ndarray, name: str = "") -> Image:
    return Image(pixels=pixels, name=name, _token=_MUTATION_TOKEN)


def _new_framestack(frames: np.ndarray, fps: int = 24, name: str = "") -> FrameStack:
    return FrameStack(frames=frames, fps=fps, name=name, _token=_MUTATION_TOKEN)


def load_image(path: str | Path) -> Image:
    from PIL import Image as PILImage

    arr = np.array(PILImage.open(path).convert("RGB"))
    return _new_image(arr, name=str(path))