Files
Trakodag/README.md
T
2026-04-16 23:00:25 +02:00

187 lines
5.5 KiB
Markdown

# trakodag
Edytor obrazu sterowany kodem. Każda warstwa to osobny program w JavaScripcie,
który operuje na pikselach wynikowego obrazu poprzedniej warstwy. Pełny dostęp
do każdego piksela, pętle, zmienne, funkcje matematyczne, dodatkowe obrazy
jako tablice pikseli.
Cała apka to jeden plik `index.html` — bez zależności, bez build-stepu.
## Uruchomienie
Otwórz `index.html` w przeglądarce:
```sh
xdg-open index.html # Linux
open index.html # macOS
firefox index.html # albo dowolna inna przeglądarka
```
## Model warstw
Warstwy tworzą **pipeline**:
```
original → layer 1 → layer 2 → … → final
```
Każda warstwa otrzymuje wynik poprzedniej jako `prev` (dla pierwszej `prev = original`)
i pisze do `output`. Wynik trafia jako `prev` do następnej warstwy. Suwak
**strength** każdej warstwy to mieszanie z wejściem: `lerp(prev, output, strength)`
— przy `0.00` warstwa jest niewidoczna, przy `1.00` w pełni nadpisuje.
Wyłączenie ikony oka pomija warstwę całkowicie.
## Interfejs
- **Load image** — załaduj obraz źródłowy (PNG, JPG, …)
- **+ Add image** — dorzuć dodatkowy obraz dostępny w kodzie jako `images[i]`
- **Run / auto-run** — odpalenie pipeline'u (auto-run debouncuje wpisywanie ~250 ms);
skrót `Ctrl/Cmd + Enter`
- **scale** — rozdzielczość robocza podglądu (`1x`, `0.5x`, `0.25x`, `0.125x`).
Per-piksel JS jest wolny, więc edytuj na np. `0.25x`, eksport zawsze leci w `1x`.
- **theme** — `blue / orange` lub `purple / sea`; wybór pamiętany w `localStorage`
- **Export PNG** — render w pełnej rozdzielczości i pobranie pliku
- **Save project / Load project** — `.json` z samymi warstwami i kodem (bez obrazu)
- **? / ⓘ Vars** — ściąga API i pełny zestaw przykładów
Najechanie kursorem na canvas pokazuje **oryginał**.
## Edytor
- Kolorowanie składni: komentarze, stringi, liczby, słowa kluczowe, nazwy API,
funkcje, operatory.
- `Tab` wstawia 2 spacje.
- Nazwy warstw: dwuklik → edycja inline.
## Code API
Zmienne dostępne w kodzie warstwy:
| Nazwa | Co to |
|---|---|
| `width`, `height` | wymiary obrazu w aktualnej rozdzielczości roboczej |
| `original` | obiekt źródłowego obrazu: `{ pixels, width, height, getPixel(x,y) }` |
| `prev` | wynik poprzedniej warstwy (dla pierwszej = `original`), ten sam kształt |
| `images[i]` | dodatkowe obrazy dorzucone przyciskiem **+ Add image** |
| `output` | `Uint8ClampedArray` RGBA długości `width*height*4` — to do tego piszesz |
Helpery:
| Nazwa | Działanie |
|---|---|
| `getPixel(x, y)` | skrót do `prev.getPixel(x, y)``{r, g, b, a}` |
| `setPixel(x, y, r, g, b, a)` | zapis piksela; `a` domyślnie `255` |
| `setPixel(x, y, p)` | zapis z obiektu `{r, g, b, a}` |
| `forEach((x, y, p) => { … })` | iteracja po wszystkich pikselach `prev`; mutuj `p` lub zwróć nowy |
| `clamp(v, lo=0, hi=255)` | obcięcie do zakresu |
| `lerp(a, b, t)` | interpolacja liniowa |
| `log(...)` | wypisanie do panelu konsoli pod edytorem |
Random:
| Nazwa | Wynik |
|---|---|
| `random1()` | float `[0, 1)` |
| `random256()` | int `[0, 255]` |
| `randomBoolean()` | `true` / `false` (50/50) |
| `randomRange(lo, hi)` | float `[lo, hi)` |
| `randomInt(lo, hi)` | int `[lo, hi]` (inclusive) |
| `random()` | alias `Math.random()` |
Matematyka — wszystko z `Math` jest dostępne pod skróconymi nazwami:
`sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `abs`, `sqrt`, `pow`,
`exp`, `floor`, `ceil`, `round`, `min`, `max`, `hypot`, `sign`, plus stałe
`PI`, `TAU`, `E`. Pełen obiekt też jest pod `Math`.
## Przykłady
Brightness +5:
```js
forEach((x, y, p) => {
p.r = clamp(p.r + 5);
p.g = clamp(p.g + 5);
p.b = clamp(p.b + 5);
});
```
Maksymalny niebieski w kolumnie x = 50:
```js
forEach((x, y, p) => {
if (x === 50) { p.r = 0; p.g = 0; p.b = 255; }
});
```
Co 500-ny piksel: pomnóż zielony kanał razy 2:
```js
forEach((x, y, p) => {
const i = y * width + x;
if (i % 500 === 0) p.g = clamp(p.g * 2);
});
```
Wklejenie obrazu `images[0]` w punkcie `(50, 100)`:
```js
const img = images[0];
const offsetX = 50, offsetY = 100;
for (let y = 0; y < img.height; y++) {
for (let x = 0; x < img.width; x++) {
const dx = x + offsetX, dy = y + offsetY;
if (dx < 0 || dx >= width || dy < 0 || dy >= height) continue;
const q = img.getPixel(x, y);
setPixel(dx, dy, q.r, q.g, q.b, q.a);
}
}
```
Sinusoidalne przesunięcie poziome:
```js
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const dx = floor(sin(y * 0.05) * 20);
const p = prev.getPixel((x + dx + width) % width, y);
setPixel(x, y, p.r, p.g, p.b, p.a);
}
}
```
Więcej przykładów w panelu `?` — basics / coordinates / random / geometry / second image.
## Format pliku projektu
`Save project` zapisuje JSON ze stanem warstw (sam kod, bez obrazu):
```json
{
"format": "trakodag.web",
"version": 1,
"savedAt": "2026-04-16T20:00:00.000Z",
"layers": [
{
"name": "base",
"code": "forEach((x,y,p) => { p.r = clamp(p.r + 5); ... });",
"visible": true,
"opacity": 1.0
}
]
}
```
Obraz wczytujesz osobno przyciskiem **Load image** — projekt jest przenośny
między różnymi obrazami.
## Wydajność
Per-piksel JS na obrazach kilkudziesięciu MP będzie zauważalnie wolny. Schemat
pracy:
1. Wczytaj obraz, zostaw `scale` na `0.25x` (lub `0.125x` dla bardzo dużych)
2. Pisz i debuguj kod na małym podglądzie
3. Przed eksportem opcjonalnie podbij `scale` żeby zobaczyć finalny render
4. **Export PNG** zawsze leci w `1x` — nie musisz nic zmieniać