187 lines
5.5 KiB
Markdown
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ć
|