PGA — Krita Python Plugins (6 cvičení)
Šest praktických cvičení: od „Hello filtru“ přes konvoluce, frekvenční filtr, barevné transformace až po dávkové zpracování a publikaci pluginu. Vše s ohledem na reálná omezení Krity (BGRA, chybějící NumPy) a výkon (ROI, blokové čtení).
Pokud jste dříve pracovali s GIMPem, může vám pomoci migrační tahák zde: PGA1.adoc
1. Slidy a materiály ke stažení
Materiál | Odkaz |
---|---|
Krita — Týden 01: Úvod & Navigace | |
Krita — Týden 02: Barvy & Soubory (extended) | |
Krita — Týden 03: Vrstvy/Masky/Štětce (extended) | |
Krita — Týden 04: Retuš (masterclass) | |
Krita — Týden 05: Engine-specific nastavení (v4) | |
Krita — Týden 05: Games export (masterclass) | |
Krita — Týden 06: Animace & závěr (extended) | |
PGA — Krita Plugins: Cvičení 2 (Konvoluce I) | PDF • ZIP |
PGA — Krita Plugins: Cvičení 3 (Konvoluce II / Gauss & Sobel) | PDF • ZIP |
PGA — Krita Plugins: Cvičení 4 (FFT / FreqLab) | PGA — Krita Plugins: Cvičení 4 (FFT / FreqLab) |
PDF • ZIP | PGA — Krita Plugins: Cvičení 5 (ColorLab) |
PGA — Krita Plugins: Cvičení 5 (ColorLab) | PDF • ZIP |
PGA — Krita Plugins: Cvičení 6 (BatchLab) | PGA — Krita Plugins: Cvičení 6 (BatchLab) |
2. Před startem — prostředí, Script Starter, cesty, importy
2.1. Umístění pluginů (pykrita)
- Windows:
%APPDATA%\krita\pykrita
(např.C:\Users\<username>\AppData\Roaming\krita\pykrita
) - Linux:
~/.local/share/krita/pykrita
- macOS:
~/Library/Application Support/Krita/pykrita
(ověřte v Help → About Krita → Resources Location)
2.2. Vytvoření pluginu — Krita Script Starter (doporučeno)
- Settings → Configure Krita → Python Plugin Manager.
- Zapněte Krita Script Starter a restartujte Kritu.
- Tools → Scripts → Krita Script Starter → vyplňte údaje → plugin se vytvoří v
pykrita/
.
2.3. Vytvoření ručně (alternativa)
Struktura:
pykrita/ my_plugin.desktop my_plugin/ __init__.py my_plugin.py
my_plugin.desktop
:
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-Python-2-Compatible=false
X-Krita-Manual=Manual.html
Name=My Plugin
Comment=This is my plugin
X-Krita-Plugin-Category=Tools
X-Krita-Enabled=true
X-Krita-Python-Module=my_plugin
my_plugin/init.py
:
from .my_plugin import MyPlugin
from krita import Krita
app = Krita.instance()
extension = MyPlugin(parent=app)
app.addExtension(extension)
my_plugin/my_plugin.py
(skeleton):
from krita import Extension
EXTENSION_ID = "pykrita_my_plugin"
MENU_ENTRY = "My Plugin"
class MyPlugin(Extension):
def __init__(self, parent):
super().__init__(parent)
self.app = parent
def setup(self):
pass # volá se při startu Krity
def createActions(self, window):
action = window.createAction(EXTENSION_ID, MENU_ENTRY, "tools/scripts")
action.triggered.connect(self.action_triggered)
def action_triggered(self):
# TODO: implementace
pass
2.4. Importy a externí moduly
- Krita (Windows) má vlastní Python → systémové balíčky (
numpy
,scipy
…) nejsou k dispozici. - Když
import
selže, plugin se často tiše nenačte (v UI nic neuvidíte). - Řešení:
- preferujte čistý Python (blokové operace,
memoryview
); - případně krita-pip (instalace balíčků do pluginu) — používat střídmě;
- debug: spouštějte Kritu z konzole, sledujte traceback; v Python Plugin Manager kontrolujte stav pluginů.
- preferujte čistý Python (blokové operace,
2.5. Přístup k pixelům (BGRA) + ROI & refresh
from krita import Krita
def active_layer_and_roi():
app = Krita.instance()
doc = app.activeDocument()
if not doc:
return None, None, (0,0,0,0)
node = doc.activeNode()
sel = doc.selection()
if sel:
x,y,w,h = sel.x(), sel.y(), sel.width(), sel.height()
else:
x,y,w,h = 0, 0, doc.width(), doc.height()
return doc, node, (x,y,w,h)
def invert_rgb_bgra_bytearray():
doc, node, (x,y,w,h) = active_layer_and_roi()
if not node or w==0 or h==0: return
# Krita dává data jako BGRA (8bit)
data = bytearray(node.pixelData(x, y, w, h))
mv = memoryview(data)
for i in range(0, len(mv), 4):
b = mv[i+0]; g = mv[i+1]; r = mv[i+2] # A = mv[i+3]
mv[i+0] = 255 - b
mv[i+1] = 255 - g
mv[i+2] = 255 - r
node.setPixelData(mv.tobytes(), x, y, w, h)
doc.refreshProjection()
# někdy pomůže toggle viditelnosti:
# node.setVisible(False); node.setVisible(True)
PGA — Krita Python Plugins: Cvičení 1 (Hello filter: plugin, akce, BGRA, výběr a bezpečná inverze)
Cíl: zprovoznit plugin, přidat akci do Tools/Scripts, číst/zapisovat BGRA, fungovat na výběr i celou vrstvu. Výstup: „Invert RGB“ s volbou „Apply to selection only“.
.1. Požadavky & prostředí
- Krita 5.x+ (doporučeno 5.2+), vestavěný Python Krity.
- Editor (VS Code / PyCharm).
- Základy Pythonu (třídy, moduly).
.2. Vytvoření pluginu (Script Starter / ručně)
Viz „Před startem“. Po vytvoření ověřte v Settings → Configure Krita → Python Plugin Manager, že je plugin Enabled.
.3. Kostra akce a bezpečná inverze
from krita import Extension
EXTENSION_ID = "pykrita_hello_krita"
MENU_PATH = "tools/scripts"
class HelloKrita(Extension):
def __init__(self, parent):
super().__init__(parent)
self.app = parent
def setup(self): pass
def createActions(self, window):
action = window.createAction(EXTENSION_ID, "Invert Colors (selection-safe)", MENU_PATH)
action.triggered.connect(self.run_invert)
def _active_doc_node_roi(self):
doc = self.app.activeDocument()
if not doc: return None, None, (0,0,0,0)
node = doc.activeNode()
sel = doc.selection()
if sel:
x,y,w,h = sel.x(), sel.y(), sel.width(), sel.height()
else:
x,y,w,h = 0,0,doc.width(),doc.height()
return doc, node, (x,y,w,h)
def run_invert(self):
doc, node, (x,y,w,h) = self._active_doc_node_roi()
if not node or w==0 or h==0: return
data = bytearray(node.pixelData(x, y, w, h)) # BGRA
mv = memoryview(data)
for i in range(0, len(mv), 4):
mv[i+0] = 255 - mv[i+0] # B
mv[i+1] = 255 - mv[i+1] # G
mv[i+2] = 255 - mv[i+2] # R
# mv[i+3] = mv[i+3] # A beze změny
node.setPixelData(mv.tobytes(), x, y, w, h)
doc.refreshProjection()
Varování:
BGRA! Zpracovávejte jen B,G,R; alfa nechte beze změny.
.4. Mini-UI (volba „pouze výběr“)
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QCheckBox, QPushButton
class InvertDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Invert Colors — Options")
self.only_selection = QCheckBox("Apply to selection only (if exists)")
ok_btn = QPushButton("OK"); cancel_btn = QPushButton("Cancel")
lay = QVBoxLayout(self); lay.addWidget(self.only_selection); lay.addWidget(ok_btn); lay.addWidget(cancel_btn)
ok_btn.clicked.connect(self.accept); cancel_btn.clicked.connect(self.reject)
Napojení na akci:
from PyQt5.QtWidgets import QApplication, QDialog
def createActions(self, window):
action = window.createAction(EXTENSION_ID, "Invert Colors (dialog)", MENU_PATH)
action.triggered.connect(self.run_invert_dialog)
def run_invert_dialog(self):
dlg = InvertDialog()
if dlg.exec_() != QDialog.Accepted: return
if dlg.only_selection.isChecked():
doc = self.app.activeDocument()
if doc and not doc.selection():
return # nic bez výběru
self.run_invert()
QApplication.processEvents()
.5. Odevzdání
- Adresář pluginu (desktop,
init.py
,hello_krita.py
) - Krátké README (kde v menu, jak spustit)
- PNG/GIF „před/po“ + screenshot dialogu
.6. Checklist kvality
- Plugin se načte (viditelný v Python Plugin Manager).
- Akce v Tools/Scripts.
- RGB invert (BGRA pořadí), A zachována.
- ROI = výběr; bez výběru celé plátno.
-
refreshProjection()
po zápisu. - Kód přehledný, komentovaný.
.7. Rubrika (0–4)
- Funkčnost • Technická správnost • Kód & struktura • UX
.8. Časté chyby & troubleshooting
- Akce není v menu → pád při
import
. Spusťte Kritu z konzole, sledujte traceback. - Mění se průhlednost → omylem upravujete A.
- Pomalé → pracujte s ROI, po řádcích/blocích.
- UI se neprokreslí →
refreshProjection()
, případně krátké vyp/zap viditelnosti vrstvy.
PGA — Krita Python Plugins: Cvičení 2 (Konvoluce I)
Autor: tým PGA :toc: macro
ZIP :toclevels: 3 :sectnums: :icons: font :source-highlighter: rouge :experimental: :lang: cs
Cíl cvičení: vytvořit plugin ConvolutionLab s obecným konvolučním jádrem (3×3; volitelně 5×5), náhledem na ROI, volbou okrajů (clamp/mirror) a normalizací jádra. Zpracovávat jen B,G,R (BGRA), alfa zachovat. Optimalizovat průchod po řádcích/blocích.
1. Připraveno z Cvičení 1
- Umíte vytvořit plugin přes Script Starter a registrovat akci do Tools/Scripts.
- Znáte BGRA pořadí kanálů a práci s ROI dle výběru.
- Ovládáte
pixelData()/setPixelData()
+refreshProjection()
.
Varování:
Bez NumPy: počítejte čistě v Pythonu. Krita na Windows používá vlastní Python – nepočítejte s numpy/scipy
.
2. Zadání
Vytvořte plugin ConvolutionLab: * UI s maticí 3×3 (editovatelná), přepínač Normalize sum, Preserve brightness, režim okraje Clamp/Mirror. * Preview na zmenšeném ROI (např. 256×256) – bez zásahu do zdrojové vrstvy. * Akce Apply – provede filtr nad ROI (nebo celou vrstvou, není-li výběr). * Volitelné: 5×5 režim a předvolby (Box blur, Gaussian approx, Sharpen, Edge/Sobel/Prewitt).
3. Struktura pluginu
Struktura:
pykrita/ convolution_lab.desktop convolution_lab/ __init__.py convolution_lab.py presets.json # volitelné (předvolby jader)
convolution_lab.desktop
(zkráceně):
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
Name=ConvolutionLab
Comment=Universal 3x3/5x5 convolution with preview
X-Krita-Enabled=true
X-Krita-Python-Module=convolution_lab
X-Krita-Plugin-Category=Tools
4. UI (PyQt) — doporučené prvky
QTableWidget
3×3 (volitelně přepínač 5×5).QCheckBox
Normalize sum – vydělí hodnoty součtem (pokud ≠ 0).QCheckBox
Preserve brightness – pokud je součet ≈ 1 (po normalizaci), zachová jas; u edge jader (součet 0) použijte posun/clip.QComboBox
Borders: Clamp / Mirror.QPushButton
Preview – render do dočasné kopie ROI (ne do zdroje!).QPushButton
Apply – aplikuje do vrstvy.QPushButton
Presets… – naplní tabulku výběrem (Box/Gauss/Sharpen/Edge).
5. Konvoluce — implementační kostra
5.1. 4.1 Utility: aktivní dokument, ROI z výběru
from krita import Krita
def active_doc_node_roi():
app = Krita.instance()
doc = app.activeDocument()
if not doc:
return None, None, (0,0,0,0)
node = doc.activeNode()
sel = doc.selection()
if sel:
x,y,w,h = sel.x(), sel.y(), sel.width(), sel.height()
else:
x,y,w,h = 0, 0, doc.width(), doc.height()
return doc, node, (x,y,w,h)
5.2. 4.2 Okrajové režimy (Clamp/Mirror)
def clamp(v, lo, hi):
return lo if v < lo else hi if v > hi else v
def mirror(i, lo, hi):
# zrcadlení do [lo,hi] včetně krajů
span = hi - lo
if span <= 0: return lo
t = (i - lo) % (2*span)
if t > span:
t = 2*span - t
return lo + t
5.3. 4.3 Vlastní konvoluční průchod (BGRA, jen B,G,R)
def convolve_bgra_block(src_bytes, w, h, kernel, ksize=3, border='clamp'):
"""
src_bytes: bytes/bytearray BGRA (w*h*4)
vrací nový bytearray BGRA
"""
assert ksize in (3,5)
dst = bytearray(len(src_bytes))
src = memoryview(src_bytes)
out = memoryview(dst)
half = ksize // 2
# předpočítej součet jádra a normalizaci
ksum = sum(kernel)
norm = 1.0
if abs(ksum) > 1e-9:
norm = 1.0 / ksum # Normalize sum
# vyber funkci okraje
edgef = clamp if border == 'clamp' else mirror
# hlavní smyčka po řádcích
for y in range(h):
for x in range(w):
accB = accG = accR = 0.0
for ky in range(ksize):
sy = y + ky - half
sy = edgef(sy, 0, h-1)
for kx in range(ksize):
sx = x + kx - half
sx = edgef(sx, 0, w-1)
k = kernel[ky*ksize + kx]
idx = (sy*w + sx) * 4
b = src[idx + 0]
g = src[idx + 1]
r = src[idx + 2]
accB += k * b
accG += k * g
accR += k * r
# normalizace
accB *= norm; accG *= norm; accR *= norm
# clamp to 0..255
B = 0 if accB < 0 else 255 if accB > 255 else int(accB + 0.5)
G = 0 if accG < 0 else 255 if accG > 255 else int(accG + 0.5)
R = 0 if accR < 0 else 255 if accR > 255 else int(accR + 0.5)
di = (y*w + x)*4
out[di + 0] = B
out[di + 1] = G
out[di + 2] = R
out[di + 3] = src[di + 3] # alfa kopie 1:1
return dst
Důležité:
Preserve brightness: u „rozostřovacích“ jader má smysl normalizovat součet na 1.
U edge jader je součet často 0 – normalizace součtem nedává smysl.
Řešení: pokud ksum==0
, neškálujte součtem a jen clipujte do 0..255 (nebo přidejte globální gain).
5.4. 4.4 Aplikace do vrstvy (ROI + refresh)
def apply_kernel_to_active_layer(kernel, ksize=3, border='clamp'):
doc, node, (x,y,w,h) = active_doc_node_roi()
if not node or w==0 or h==0: return
src = bytearray(node.pixelData(x, y, w, h))
dst = convolve_bgra_block(src, w, h, kernel, ksize, border)
node.setPixelData(bytes(dst), x, y, w, h)
doc.refreshProjection()
6. Předvolby (doporučené matice)
- Box blur 3×3:
[[1,1,1],[1,1,1],[1,1,1]]
Normalize sum: ON - Gaussian approx 3×3:
[[1,2,1],[2,4,2],[1,2,1]]
Normalize sum: ON - Sharpen 3×3:
[[0,-1,0],[-1,5,-1],[0,-1,0]]
Normalize sum: OFF (součet 1; brightness OK) - Edge (Laplacian):
[[0,1,0],[1,-4,1],[0,1,0]]
nebo[[1,1,1],[1,-8,1],[1,1,1]]
Normalize sum: OFF (součet 0) - Prewitt/Sobel (na hrany X/Y) – volitelně jako dvojice jader
7. Preview na ROI (doporučený postup)
- Získejte ROI (výběr nebo celé plátno) a zmenšete jej (např. nearest/box) na max 256 px delší strana.
- Spočítejte konvoluci nad zmenšeným obrazem.
- Výsledek zobrazte v
QLabel
(pixmapa) v dialogu. - Nezapisujte do zdroje, dokud uživatel nestiskne Apply.
8. Časování & výkon (tipy)
- Řádkový průchod +
memoryview
(viz výše) místo „pixel po pixelu“ s indexováním listů – citelně rychlejší. - U 5×5 je vhodné vynechat výpočty pro A kanál (kopírovat).
- ThreadPoolExecutor (po řádcích/blocích) může pomoci jen omezeně (GIL); prioritu dejte čistému Pythonu a menšímu overheadu.
- Pro velmi velká plátna nabídněte progress a možnost Cancel.
9. Odevzdání (co má být v repu)
- ConvolutionLab (adresář pluginu) –
.desktop
,init.py
,convolution_lab.py
. - README: instalace, kde v menu, popis parametrů, známá omezení.
- Předvolby (JSON) – volitelné.
- Ukázky: 3–4 PNG „před/po“ (blur, sharpen, edge).
10. Checklist kvality
- Plugin se načte, akce je v Tools/Scripts.
- Matice 3×3 editovatelná, Normalize sum funguje.
- Clamp/Mirror okrajové chování bez artefaktů.
- Preview je rychlý a nepíše do zdroje.
- Apply zpracuje jen ROI dle výběru (jinak celé).
- Zpracovává se jen B,G,R, A se kopíruje 1:1.
-
refreshProjection()
po zápisu.
11. Hodnocení (rubrika 0–4)
- Funkčnost (0–4): správné výsledky, okraje, normalizace, preview+apply.
- Výkon (0–4): plynulé preview, rozumný čas na 2–4k, UI nepadá.
- Kód (0–4): čitelný, rozdělený na UI/logic, komentáře u těžších částí.
- UX (0–4): jasné popisky, bezpečné defaulty, předvolby (aspoň 3).
12. Rozšíření (bonus)
- 5×5 režim (přepínač) a odpovídající jádra (Gauss 5×5).
- Separable mód pro Gauss (X→Y) — příprava na Cvičení 3 (rychlost).
- Absolute/Gain pro edge detekci.
- Apply on copy – vytvořit duplikát vrstvy a filtr aplikovat na kopii (nedestruktivně).
13. Troubleshooting
- Tmavý/světlý výsledek po blur: chybějící Normalize sum (musí být 1).
- Zubaté okraje: špatné okrajové vzorkování – ověřte Clamp/Mirror.
- Pomalé/freeze: vypněte preview na plné velikosti; dělejte downsample.
- Nic se nezmění: zapisujete do jiné vrstvy, nebo chybí
refreshProjection()
. - Rozpad alfa: upravujete A kanál – neměňte alfa, kopírujte ji.
14. Příloha — ukázka napojení UI na výpočet (zkráceno)
class ConvolutionDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
# ... vytvořte table 3x3, checky a combobox ...
# self.table, self.chk_norm, self.chk_preserve, self.cmb_border
# self.btn_preview, self.btn_apply
def read_kernel(self):
k = []
for r in range(3):
for c in range(3):
try:
k.append(float(self.table.item(r,c).text()))
except:
k.append(0.0)
return k
def run_convolution(self):
dlg = ConvolutionDialog()
if dlg.exec_() != QDialog.Accepted:
return
kernel = dlg.read_kernel()
border = 'mirror' if dlg.cmb_border.currentText().lower().startswith('mir') else 'clamp'
apply_kernel_to_active_layer(kernel, 3, border)
PGA — Krita Python Plugins: Cvičení 3 (Konvoluce II – rychlost)
Autor: tým PGA :toc: macro
ZIP :toclevels: 3 :sectnums: :icons: font :source-highlighter: rouge :experimental: :lang: cs
Cíl cvičení: navázat na Cvičení 2 a urychlit filtry pomocí separabilního Gaussova rozostření (1D X→Y), implementovat Sobel (Gx, Gy) včetně magnitude (a volitelně orientace), a udělat benchmark (2k/4k, ROI). Stále platí: BGRA, zpracovat jen B,G,R, A kopírovat.
1. Předpoklady
- Zvládáte plugin z Cvičení 1 (akce, ROI, BGRA, refresh).
- Máte kostru z Cvičení 2 (konvoluce 3×3, okraje clamp/mirror, preview).
- Nepočítáme s NumPy/Scipy (čistý Python).
Varování:
Windows Krita používá vlastní Python → import NumPy nejspíš selže (plugin se tiše nenačte). Cvičení proto řešte čistým Pythonem.
2. Zadání
Vytvořte plugin FastFilters:
* Panel Separable Gaussian: sigma
, radius
(nebo auto radius = ceil(3σ)
), okraj Clamp/Mirror, Preview + Apply.
* Panel Sobel: volba výstupu: Gx
, Gy
, Magnitude
, (volitelně Orientation
v ° nebo barevná vizualizace), Preview + Apply.
* Panel Benchmark: změří časy pro 2–3 rozlišení / ROI; vypíše tabulku (CSV do schránky nebo do logu).
* Stále BGRA, A kopírujte 1:1, vše selection-safe (ROI).
3. Separable Gauss — teorie do praxe
Gaussovo jádro je separabilní → 2 průchody 1D: nejprve horizontálně (řádky), pak vertikálně (sloupce). Tím snížíte složitost z O(k² * N)
na O(2k * N)
.
3.1. 2.1 Generátor 1D jádra
import math
def gaussian_kernel_1d(sigma, radius=None):
if sigma <= 0: return [1.0]
if radius is None:
radius = int(math.ceil(3.0 * sigma)) # ~99.7 % hmoty
w = 2*radius + 1
k = [0.0]*w
s2 = 2.0 * sigma * sigma
acc = 0.0
for i in range(w):
x = i - radius
v = math.exp(-(x*x)/s2)
k[i] = v
acc += v
# normalizace na 1.0
for i in range(w):
k[i] /= acc
return k, radius
3.2. 2.2 Okraje (re-use z Cvičení 2)
Použijte stejné clamp/mirror funkce. U separabilního průchodu budete sahat na indexy (x±r), (y±r).
3.3. 2.3 1D konvoluce přes řádky a sloupce
def convolve_rows_bgr(src_bytes, w, h, kernel, radius, border='clamp'):
src = memoryview(src_bytes)
dst = bytearray(len(src_bytes))
out = memoryview(dst)
edgef = clamp if border == 'clamp' else mirror
for y in range(h):
row_off = y * w * 4
for x in range(w):
accB = accG = accR = 0.0
for k in range(-radius, radius+1):
sx = edgef(x + k, 0, w-1)
i = row_off + sx*4
# BGRA
b = src[i+0]; g = src[i+1]; r = src[i+2]
wgt = kernel[k + radius]
accB += wgt * b
accG += wgt * g
accR += wgt * r
di = row_off + x*4
out[di+0] = 0 if accB<0 else 255 if accB>255 else int(accB+0.5)
out[di+1] = 0 if accG<0 else 255 if accG>255 else int(accG+0.5)
out[di+2] = 0 if accR<0 else 255 if accR>255 else int(accR+0.5)
out[di+3] = src[di+3] # alfa
return dst
def convolve_cols_bgr(src_bytes, w, h, kernel, radius, border='clamp'):
src = memoryview(src_bytes)
dst = bytearray(len(src_bytes))
out = memoryview(dst)
edgef = clamp if border == 'clamp' else mirror
for y in range(h):
for x in range(w):
accB = accG = accR = 0.0
for k in range(-radius, radius+1):
sy = edgef(y + k, 0, h-1)
i = (sy*w + x)*4
b = src[i+0]; g = src[i+1]; r = src[i+2]
wgt = kernel[k + radius]
accB += wgt * b
accG += wgt * g
accR += wgt * r
di = (y*w + x)*4
out[di+0] = 0 if accB<0 else 255 if accB>255 else int(accB+0.5)
out[di+1] = 0 if accG<0 else 255 if accG>255 else int(accG+0.5)
out[di+2] = 0 if accR<0 else 255 if accR>255 else int(accR+0.5)
out[di+3] = src[di+3]
return dst
def gaussian_blur_separable(src_bytes, w, h, sigma, radius=None, border='clamp'):
kernel, radius = gaussian_kernel_1d(sigma, radius)
tmp = convolve_rows_bgr(src_bytes, w, h, kernel, radius, border)
out = convolve_cols_bgr(tmp, w, h, kernel, radius, border)
return out
3.4. 2.4 Aplikace do vrstvy (ROI + refresh)
def apply_gaussian_to_active_layer(sigma, radius=None, border='clamp'):
doc, node, (x,y,w,h) = active_doc_node_roi()
if not node or w==0 or h==0: return
src = bytearray(node.pixelData(x, y, w, h))
dst = gaussian_blur_separable(src, w, h, sigma, radius, border)
node.setPixelData(bytes(dst), x, y, w, h)
doc.refreshProjection()
4. Sobel (Gx, Gy, Magnitude, Orientation)
Sobel funguje jako dvojice 3×3 jader. Počítejte gradienty pro každý z RGB kanálů (nebo na luminanci – rychlejší/čistší), a pak z toho odvozujte magnitudu (sílu hrany).
4.1. 3.1 Jádra
SOBEL_X = [-1,0,1, -2,0,2, -1,0,1]
SOBEL_Y = [-1,-2,-1, 0,0,0, 1,2,1]
4.2. 3.2 Výpočet Gx/Gy a magnitude (na luminanci Y – doporučeno)
def rgb_to_luma(r,g,b):
# Rec.709
return 0.2126*r + 0.7152*g + 0.0722*b
def sobel_luma_bgra(src_bytes, w, h, border='clamp', want='magnitude'):
src = memoryview(src_bytes)
out = bytearray(len(src_bytes))
dst = memoryview(out)
edgef = clamp if border == 'clamp' else mirror
kx = SOBEL_X; ky = SOBEL_Y
for y in range(h):
for x in range(w):
gx = gy = 0.0
# 3x3 okno
idxk = 0
for ky_off in (-1,0,1):
sy = edgef(y+ky_off, 0, h-1)
for kx_off in (-1,0,1):
sx = edgef(x+kx_off, 0, w-1)
i = (sy*w + sx)*4
b,g,r = src[i+0], src[i+1], src[i+2]
yv = rgb_to_luma(r,g,b)
gx += kx[idxk] * yv
gy += ky[idxk] * yv
idxk += 1
mag = (gx*gx + gy*gy) ** 0.5
# normalizace jednoduchým škálováním
m = int(max(0, min(255, mag)))
di = (y*w + x)*4
if want == 'gx':
v = int(max(0, min(255, gx + 128))) # posun kvůli vizualizaci
dst[di+0]=dst[di+1]=dst[di+2]=v
elif want == 'gy':
v = int(max(0, min(255, gy + 128)))
dst[di+0]=dst[di+1]=dst[di+2]=v
elif want == 'orientation':
# orientace v rozsahu 0..180° → mapujte do 0..255
import math
ang = math.degrees(math.atan2(gy, gx)) # -180..180
ang = abs(ang) # 0..180
v = int(ang * (255.0/180.0))
dst[di+0]=dst[di+1]=dst[di+2]=v
else: # magnitude (default)
dst[di+0]=dst[di+1]=dst[di+2]=m
dst[di+3] = src[di+3] # alfa
return out
4.3. 3.3 Aplikace Sobelu
def apply_sobel_to_active_layer(mode='magnitude', border='clamp'):
doc, node, (x,y,w,h) = active_doc_node_roi()
if not node or w==0 or h==0: return
src = bytearray(node.pixelData(x, y, w, h))
dst = sobel_luma_bgra(src, w, h, border, want=mode)
node.setPixelData(bytes(dst), x, y, w, h)
doc.refreshProjection()
5. Preview & UI
- Preview vždy spočítejte na downsample kopii ROI (delší strana max ~256 px).
- U Gauss σ udělejte slider + číslo; u Sobel přepínače
gx/gy/magnitude/orientation
. - Border: stejný
Clamp/Mirror
jako v Cvičení 2.
6. Benchmark panel
Změřte čas jednotlivých kroků a vypište tabulku (log/CSV).
import time
def bench_filter(func, label, x,y,w,h, repeats=1):
t0 = time.perf_counter()
for _ in range(repeats):
func()
t1 = time.perf_counter()
dt = (t1 - t0) / repeats
print(f"{label}; {w}x{h}; {dt*1000:.1f} ms")
return dt
Příklad běhu: * Gauss σ=1.6 (separable) – ROI 1920×1080. * Gauss σ=3.0 (separable) – ROI 3840×2160. * Sobel magnitude – ROI 1920×1080. Zapište do README krátkou tabulku (rozlišení, čas, border).
7. Odevzdání
- Adresář FastFilters (desktop,
init.py
,fast_filters.py
). - README: jak spustit, parametry, tabulka benchmarku (alespoň 2 rozlišení), známá omezení.
- Ukázky: PNG „před/po“ (Gauss, Sobel magnitude).
8. Checklist kvality
- Gauss je separabilní (dva průchody 1D), normalizovaný kernel.
- Okraje Clamp/Mirror korektní.
- Sobel:
gx/gy/magnitude
(volitelněorientation
) funguje. - Preview je rychlý a nezasahuje do zdroje.
- Apply respektuje ROI dle výběru.
- A kopie 1:1 (neměnit).
- Benchmark – srozumitelné výsledky v README.
-
refreshProjection()
po zápisu.
9. Hodnocení (rubrika 0–4)
- Funkčnost (0–4): správné výsledky Gauss/Sobel, preview+apply, okraje.
- Výkon (0–4): separabilita přináší výrazné zrychlení; benchmark doručen.
- Kód (0–4): čisté oddělení UI/logic, komentáře u klíčových částí, opakované využití utilit.
- UX (0–4): přehledné ovládání, bezpečné defaulty, rychlý preview.
10. Rozšíření (bonus)
- Unsharp mask:
out = original + amount * (original - gaussian_blur)
. - Adaptive σ: různá σ pro X/Y (anisotropic blur – stále separabilní).
- Sobel na barvu: magnitude jako max/avg přes R,G,B (namísto luminance).
- Autoscale magnitude: najdi lokální maximum a škáluj mapu do 0..255.
11. Troubleshooting
- „Gauss tmaví/světlá“ – jádro není normalizované (součet ≠ 1).
- Artefakty na hranách – zkontrolujte správné clamp/mirror u indexů.
- Pomalý preview – nedělejte ho na plnou velikost; downsample.
- Změněná průhlednost – omylem upravujete A; A vždy kopírujte.
- UI zamrzá – pro dlouhé operace použijte QProgressDialog +
QApplication.processEvents()
.
PGA — Krita Python Plugins: Cvičení 4 (Frekvenční filtr – FFT/DFT)
Autor: tým PGA :toc: macro
ZIP :toclevels: 3 :sectnums: :icons: font :source-highlighter: rouge :experimental: :lang: cs
Cíl cvičení: napsat plugin FreqLab pro náhled spektra (log-magnitude) vybrané oblasti (ROI) a aplikovat low-pass / high-pass / band-pass filtr. Primárně počítejte přes NumPy FFT (pokud je dostupné), jinak použijte fallback DFT na malém náhledu (např. 64×64). Zpracování dělejte selection-safe, respektujte BGRA (pracujte na luminanci nebo po kanálech) a nabídněte Preview vs. Apply.
1. Předpoklady
- Umíte pracovat s ROI (dle výběru) a číst/zapisovat pixely (BGRA) – viz Cvičení 1–3.
- Znáte základní PyQt dialogy (slidery, combobox, tlačítka).
- Víte, že na Windows má Krita vlastní Python →
numpy
nemusí být k dispozici.
Varování:
Pokud import numpy
selže, plugin se může tiše nenačíst. Použijte try/except a přepněte se do fallback režimu s malou DFT jen pro náhled (např. 64×64), zatímco ostré Apply uděláte v prostorové doméně (např. pomocí Gauss blur + unsharp ap.), nebo necháte Apply fungovat jen, když je NumPy dostupné. Uveďte to zřetelně v UI.
2. Zadání
Vytvořte plugin FreqLab: * zobrazí log-magnitude spektrum aktuálního ROI, * umožní vybrat filtr: Low-pass, High-pass, Band-pass (kruhové masky ve frekvenční doméně), * parametry: radius (LP/HP), inner/outer radius (BP), volba okna pro potlačení zvonění (None/Hann), * přepínač Process: Luma only / Per-channel (RGB), * Preview (rychlý náhled) a Apply (aplikace do vrstvy nebo na kopii vrstvy), * bezpečný režim: pokud není NumPy, UI to oznámí a Preview běží na 64×64 (DFT, jen luma), Apply: (a) nedostupné, nebo (b) alternativní aproximace (např. Gauss blur versus unsharp).
3. Teorie – stručně pro praxi
- FFT (rychlá Fourierova transformace) převádí obraz do frekvenční domény: nízké frekvence ~ hladké změny, vysoké ~ hrany/detail.
- Pro vizualizaci magnitude se používá log (např.
log(1+|F|)
) a fftshift (DC doprostřed). - Kruhové masky:
- Low-pass: nechá jen frekvence s
r ≤ R
. - High-pass: nechá
r ≥ R
. - Band-pass: nechá
R_in ≤ r ≤ R_out
.
- Low-pass: nechá jen frekvence s
- Okno (Hann/Hamming) v prostoru před FFT zmírní zvonění (ringing) způsobené ostrým ořezem.
- Po filtraci: iFFT → reálná část → normalizace/clamp → zápis do BGRA.
4. UI návrh (PyQt)
QComboBox Filter:
Low-pass, High-pass, Band-pass.Radius
(pro LP/HP),Inner/Outer Radius
(pro BP) – v pixelech frekvenční mřížky (relativní k menšímu rozměru FFT).Window:
None / Hann.Process:
Luma only / Per-channel (RGB).- Tlačítka: Preview, Apply, To New Layer (volitelně), Close.
- Panel Status/Hint: dostupnost NumPy, velikost ROI, fallback mód.
5. Implementační kostry
5.1. 4.1 Bezpečný import NumPy
def try_import_numpy():
try:
import numpy as np
return np
except Exception:
return None
NP = try_import_numpy()
HAS_NUMPY = NP is not None
5.2. 4.2 Získání ROI + převod na lumu
from krita import Krita
def get_doc_node_roi():
app = Krita.instance()
doc = app.activeDocument()
if not doc:
return None, None, (0,0,0,0)
node = doc.activeNode()
sel = doc.selection()
if sel:
x,y,w,h = sel.x(), sel.y(), sel.width(), sel.height()
else:
x,y,w,h = 0,0,doc.width(), doc.height()
return doc, node, (x,y,w,h)
def bgra_to_luma_bytes(bgra, w, h):
# Rec.709 luma
out = bytearray(w*h)
mv = memoryview(bgra)
for i in range(0, len(mv), 4):
b = mv[i+0]; g = mv[i+1]; r = mv[i+2]
y = 0.2126*r + 0.7152*g + 0.0722*b
out[i//4] = 0 if y<0 else 255 if y>255 else int(y+0.5)
return out
5.3. 4.3 FFT náhled (NumPy varianta)
def fft_preview_numpy(gray_u8, w, h, window='none'):
import numpy as np
g = np.frombuffer(gray_u8, dtype=np.uint8).astype(np.float32).reshape(h, w)
# okno (proti ringing)
if window.lower().startswith('hann'):
wy = np.hanning(h); wx = np.hanning(w)
g = g * wy[:,None] * wx[None,:]
# FFT
F = np.fft.fft2(g)
Fshift = np.fft.fftshift(F)
mag = np.log1p(np.abs(Fshift))
mag /= mag.max() if mag.max() > 1e-9 else 1.0
return (mag * 255.0).astype(np.uint8)
5.4. 4.4 Kruhové masky ve frekvenční doméně
def make_circular_mask(h, w, mode, r_in_px=0, r_out_px=10):
import numpy as np
cy, cx = h//2, w//2
Y, X = np.ogrid[:h, :w]
R = np.sqrt((Y-cy)**2 + (X-cx)**2)
M = np.zeros((h,w), dtype=np.float32)
if mode == 'lowpass':
M[R <= r_out_px] = 1.0
elif mode == 'highpass':
M[R >= r_out_px] = 1.0
else: # bandpass
M[(R >= r_in_px) & (R <= r_out_px)] = 1.0
return M
5.5. 4.5 Aplikace filtru (NumPy) – luma only
def apply_fft_filter_numpy(gray_u8, w, h, mode, r1, r2, window='none'):
import numpy as np
g = np.frombuffer(gray_u8, dtype=np.uint8).astype(np.float32).reshape(h, w)
if window.lower().startswith('hann'):
wy = np.hanning(h); wx = np.hanning(w)
g = g * wy[:,None] * wx[None,:]
F = np.fft.fft2(g)
Fs = np.fft.fftshift(F)
if mode == 'bandpass':
M = make_circular_mask(h, w, 'bandpass', r_in_px=r1, r_out_px=r2)
elif mode == 'lowpass':
M = make_circular_mask(h, w, 'lowpass', r_out_px=r2)
else:
M = make_circular_mask(h, w, 'highpass', r_out_px=r2)
Fs_filtered = Fs * M
Fi = np.fft.ifftshift(Fs_filtered)
out = np.fft.ifft2(Fi).real
# normalizace do 0..255
mn, mx = float(out.min()), float(out.max())
if mx - mn < 1e-9:
out[:] = 0
else:
out = (out - mn) * (255.0/(mx-mn))
return out.astype(np.uint8)
5.6. 4.6 Zápis do vrstvy (BGRA) – luma merge
def merge_luma_into_bgra(bgra, luma_u8, w, h):
# nahradí luminanci; jednoduchá varianta: přemapuje RGB do stejné Y
mv = memoryview(bgra)
out = bytearray(len(mv))
for i in range(0, len(mv), 4):
Y = luma_u8[i//4]
# ponecháme barvu přibližně via scale ke stejné světlosti:
# jednoduché: všem RGB dáme Y (grayscale look)
out[i+0] = Y
out[i+1] = Y
out[i+2] = Y
out[i+3] = mv[i+3] # alfa
return out
5.7. 4.7 Fallback DFT (bez NumPy) – jen pro malý náhled (např. 64×64)
def tiny_dft_2d(gray_u8, n):
# gray_u8: n*n uint8
# návrat: komplexní 2D pole (separovaně re/im v Pythonu)
import math
g = [float(gray_u8[i]) for i in range(n*n)]
F_re = [0.0]*(n*n); F_im = [0.0]*(n*n)
two_pi = 2.0*math.pi
for v in range(n):
for u in range(n):
sum_re = 0.0; sum_im = 0.0
for y in range(n):
for x in range(n):
idx = y*n + x
ang = two_pi*((u*x + v*y)/n)
val = g[idx]
sum_re += val*math.cos(-ang)
sum_im += val*math.sin(-ang)
F_re[v*n + u] = sum_re
F_im[v*n + u] = sum_im
return F_re, F_im
def dft_log_magnitude(F_re, F_im, n):
import math
mag = [0.0]*(n*n)
for i in range(n*n):
mag[i] = math.log1p((F_re[i]**2 + F_im[i]**2)**0.5)
mx = max(mag) if mag else 1.0
out = bytearray(n*n)
for i,m in enumerate(mag):
out[i] = int(255.0*(m/mx)) if mx>1e-9 else 0
return out
Upozornění:
DFT je kvadraticky pomalé. Používejte jen pro náhled do cca 64×64. Ostré Apply v DFT fallback režimu raději zakažte nebo nabídněte alternativu v prostorové doméně.
6. Preview workflow
- Získejte ROI (výběr → bbox; jinak celé plátno).
- Downsamplujte (např.
max_side = 256
s NumPy,64
bez NumPy). - Vypočítejte spektrum a zobrazte log-magnitude v
QLabel
(pixmapa). - Při změně parametrů (radius/typ filtru) přepočítejte preview.
7. Apply workflow
- Luma only: spočítejte lumu (plná velikost), FFT→mask→iFFT→normalizace → napište zpět jako grayscale RGB (A kopie).
- Per-channel: rozdělit BGRA na B,G,R → FFT per-kanál → maska → iFFT → složit zpět.
- Volitelné: Apply to new layer (duplikujte vrstvu a efekt proveďte na kopii).
- Po zápisu:
doc.refreshProjection()
; při vizuálním problému krátce přepněte viditelnost vrstvy.
8. Odevzdání
- Adresář pluginu FreqLab (
.desktop
,init.py
,freq_lab.py
). - README: dostupnost NumPy, fallback chování, limity, postup instalace.
- GIF/PNG ukázky: originál, log-magnitude náhled, LP/HP/BP výsledek.
- Volitelně: preset JSON s posledními parametry (QSettings/JSON).
9. Checklist kvality
- UI jasně ukazuje, zda běží NumPy nebo fallback.
- Preview rychle reaguje (downsample; 256/64).
- Apply funguje (luma only; per-channel volitelně).
- A kanál zachován 1:1, zápis pouze do B,G,R.
-
refreshProjection()
po zápisu; bez pádů na velkých ROI. - Dokumentace limitů (ringing, okno, bez NumPy).
10. Hodnocení (rubrika 0–4)
- Funkčnost (0–4): náhled spektra, LP/HP/BP filtr, Apply (alespoň luma).
- UX (0–4): srozumitelné ovládání, status/dostupnost NumPy, rychlé preview.
- Technika (0–4): správná normalizace, fftshift/log, ošetření okna.
- Kód (0–4): oddělené části (I/O, FFT, UI), fallback bez NumPy.
11. Rozšíření (bonus)
- Band-stop (notch) s možností vyříznout úzké pásmo.
- Volná maska: kreslení masky ve spektru (myší) → aplikace.
- Hybridní Apply v DFT fallbacku: přepočítat po dlaždicích (tiles) 128×128 pomocí FFT, pokud se podaří dynamicky načíst NumPy (např. přes krita-pip).
12. Troubleshooting
- „Plugin se nenačetl“ – patrně selhal
import numpy
. Obalte import try/except a ukažte fallback status. - „Černé/špatné spektrum“ – chybí
log1p
nebo normalizace; zkontrolujtefftshift
. - „Zvonění po filtraci“ – zapněte Hann window nebo použijte hladší filtr (Gauss v frekvenční doméně).
- „Barvy divné po Apply (per-channel)“ – pozor na různé rozsahy/normalizaci u kanálů; sjednoťte škálu.
- „UI zamrzá“ – náhled držte malý; při Apply ukažte QProgressDialog a
processEvents()
.
13. Příloha — mini-skeleton pluginu (zkráceno)
# freq_lab.py
from krita import Extension
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QComboBox, QSpinBox, QVBoxLayout, QHBoxLayout
class FreqLab(Extension):
def __init__(self, parent):
super().__init__(parent)
self.app = parent
def setup(self): pass
def createActions(self, window):
act = window.createAction("pga_freq_lab", "FreqLab (FFT/DFT)", "tools/scripts")
act.triggered.connect(self.show_dialog)
def show_dialog(self):
dlg = FreqDialog(self.app)
dlg.exec_()
class FreqDialog(QDialog):
def __init__(self, app, parent=None):
super().__init__(parent)
self.app = app
self.setWindowTitle("FreqLab — FFT/DFT")
self.cmb_filter = QComboBox(); self.cmb_filter.addItems(["Low-pass","High-pass","Band-pass"])
self.sp_r1 = QSpinBox(); self.sp_r1.setRange(1, 2048); self.sp_r1.setValue(8)
self.sp_r2 = QSpinBox(); self.sp_r2.setRange(1, 4096); self.sp_r2.setValue(24)
self.cmb_window = QComboBox(); self.cmb_window.addItems(["None","Hann"])
self.btn_preview = QPushButton("Preview")
self.btn_apply = QPushButton("Apply")
self.lbl_img = QLabel("Spectrum preview here")
lay = QVBoxLayout(self)
row = QHBoxLayout(); row.addWidget(self.cmb_filter); row.addWidget(self.sp_r1); row.addWidget(self.sp_r2); lay.addLayout(row)
row2= QHBoxLayout(); row2.addWidget(self.cmb_window); row2.addWidget(self.btn_preview); row2.addWidget(self.btn_apply); lay.addLayout(row2)
lay.addWidget(self.lbl_img)
self.btn_preview.clicked.connect(self.on_preview)
self.btn_apply.clicked.connect(self.on_apply)
def on_preview(self):
# 1) get ROI + downsample; 2) compute spectrum (NumPy or DFT fallback); 3) set pixmap to lbl_img
pass
def on_apply(self):
# if NumPy: compute filtered image on full ROI and write back; else: warn or use spatial-domain fallback
pass
PGA — Krita Python Plugins: Cvičení 5 (ColorLab — Sepia, Grayscale, Curves, HSL, Gradient Map)
Autor: tým PGA :toc: macro
ZIP :toclevels: 3 :sectnums: :icons: font :source-highlighter: rouge :experimental: :lang: cs
Cíl cvičení: vytvořit plugin ColorLab, který bezpečně a rychle provádí barevné transformace nad ROI: grayscale (více metod), sepia (3×3 matice), HSL/HSV úpravy, Curves/Levels (přes LUT), a Gradient Map (toning). Důraz na BGRA pořadí, A beze změny, preview na zmenšeném ROI a Apply na plnou oblast.
1. Předpoklady
- Zvládáte skeleton pluginu (Cvičení 1) a práci s ROI +
pixelData()/setPixelData()
(Cvičení 1–3). - Umíte postavit jednoduché PyQt dialogy a číst hodnoty ze sliderů/comboboxů.
- Víte, že na Windows má Krita vlastní Python (bez NumPy) → vše řešíme čistým Pythonem.
2. Zadání
Vytvořte ColorLab s těmito bloky: * Grayscale — režimy: Average, Luma 601, Luma 709 (doporučeno), Desaturate (min/max průměr). * Sepia — 3×3 barevná matice (viz níže), intenzita (mix původní ↔ sepia). * Curves/Levels — Master křivka (0–255) + volitelně R/G/B; implementace přes LUT (256 položek). * HSL/HSV — Hue shift (–180..+180°), Saturation (0..200 %), Lightness/Value (±). * Gradient Map — mapování luminance → barva (2–5 uzlů s barvou a pozicí 0..1). * Preview (na zmenšeném ROI) a Apply (na plné ROI), přepínač Apply on copy.
3. UI návrh (PyQt)
- Tabbed/accordion layout:
- Grayscale:
ComboBox
s metodou +Strength
(0..100 %, mix s původní barvou). - Sepia:
Strength
(0..100 %), Matrix preset (čitelné hodnoty). - Curves/Levels: pro zjednodušení v tomto cvičení 3 body (stíny, střed, světla) → z nich vygenerujte LUT 256; volitelně přepínač Master/R/G/B.
- HSL/HSV: tři slidery (Hue Shift, Saturation, Lightness/Value), přepínač HSL/HSV.
- Gradient Map: 2–5 uzlů (pozice 0..1, barva #RRGGBB), interpolace linear.
- Grayscale:
- Tlačítka: Preview, Apply, Apply on copy, Reset, Close.
- Status řádek: velikost ROI, downsample faktor, čas posledního preview.
4. Implementace — stavebnice
4.1. 3.1 ROI & čtení/zápis (BGRA)
def get_doc_node_roi(app):
doc = app.activeDocument()
if not doc: return None, None, (0,0,0,0)
node = doc.activeNode()
sel = doc.selection()
if sel:
x,y,w,h = sel.x(), sel.y(), sel.width(), sel.height()
else:
x,y,w,h = 0, 0, doc.width(), doc.height()
return doc, node, (x,y,w,h)
4.2. 3.2 Grayscale (metody)
Koeficienty luma: * Rec.601: Y = 0.299 R + 0.587 G + 0.114 B * Rec.709: Y = 0.2126 R + 0.7152 G + 0.0722 B (doporučeno) * Average: (R+G+B)/3 * Desaturate: (max(R,G,B)+min(R,G,B))/2
def gray_pixel(r,g,b, mode="709"):
if mode == "avg":
return (r+g+b)//3
if mode == "desat":
return (max(r,g,b) + min(r,g,b))//2
if mode == "601":
return int(0.299*r + 0.587*g + 0.114*b + 0.5)
# default 709
return int(0.2126*r + 0.7152*g + 0.0722*b + 0.5)
def apply_grayscale_bgra_block(bytes_in, w, h, mode="709", strength=1.0):
data = bytearray(bytes_in)
mv = memoryview(data)
for i in range(0, len(mv), 4):
b,g,r,a = mv[i+0], mv[i+1], mv[i+2], mv[i+3]
y = gray_pixel(r,g,b, mode)
if strength >= 0.999:
mv[i+0] = mv[i+1] = mv[i+2] = y
else:
mv[i+0] = int((1-strength)*b + strength*y + 0.5)
mv[i+1] = int((1-strength)*g + strength*y + 0.5)
mv[i+2] = int((1-strength)*r + strength*y + 0.5)
mv[i+3] = a
return data
4.3. 3.3 Sepia (3×3 matice + mix)
Běžně používané sepia jádro:
0.393 | 0.769 | 0.189 |
0.349 | 0.686 | 0.168 |
0.272 | 0.534 | 0.131 |
SEPIA = (
(0.393, 0.769, 0.189),
(0.349, 0.686, 0.168),
(0.272, 0.534, 0.131),
)
def mat3_apply(r,g,b, M):
r2 = M[0][0]*r + M[0][1]*g + M[0][2]*b
g2 = M[1][0]*r + M[1][1]*g + M[1][2]*b
b2 = M[2][0]*r + M[2][1]*g + M[2][2]*b
return int(min(255,max(0,r2))+0.5), int(min(255,max(0,g2))+0.5), int(min(255,max(0,b2))+0.5)
def apply_sepia_bgra_block(bytes_in, w, h, strength=1.0, M=SEPIA):
data = bytearray(bytes_in); mv = memoryview(data)
s = max(0.0, min(1.0, strength))
for i in range(0, len(mv), 4):
b,g,r,a = mv[i], mv[i+1], mv[i+2], mv[i+3]
r2,g2,b2 = mat3_apply(r,g,b, M)
if s >= 0.999:
mv[i], mv[i+1], mv[i+2] = b2, g2, r2
else:
mv[i] = int((1-s)*b + s*b2 + 0.5)
mv[i+1] = int((1-s)*g + s*g2 + 0.5)
mv[i+2] = int((1-s)*r + s*r2 + 0.5)
mv[i+3] = a
return data
4.4. 3.4 Curves/Levels → LUT 256
Zjednodušení: použijte 3 kontrolní body (in: 0, mid, 255) → linear interpolace do LUT.
def build_curve_lut(p0=(0,0), p1=(128,128), p2=(255,255)):
# body jsou v rozsahu 0..255
x0,y0 = p0; x1,y1 = p1; x2,y2 = p2
lut = [0]*256
for x in range(0, x1+1):
t = (x - x0) / max(1, (x1 - x0))
y = int((1-t)*y0 + t*y1 + 0.5)
lut[x] = max(0, min(255, y))
for x in range(x1+1, 256):
t = (x - x1) / max(1, (x2 - x1))
y = int((1-t)*y1 + t*y2 + 0.5)
lut[x] = max(0, min(255, y))
return lut
def apply_lut_bgra_block(bytes_in, w, h, lutR, lutG, lutB):
data = bytearray(bytes_in); mv = memoryview(data)
for i in range(0, len(mv), 4):
b,g,r,a = mv[i], mv[i+1], mv[i+2], mv[i+3]
mv[i] = lutB[b]
mv[i+1] = lutG[g]
mv[i+2] = lutR[r]
mv[i+3] = a
return data
4.5. 3.5 HSL/HSV (Hue/Sat/Lightness or Value)
Pro přesnost preferujte HSL pro „Lightness“ a HSV pro „Value“. Níže je rychlá integer-friendly varianta (stačí na účely kurzu).
def rgb_to_hsv_i(r,g,b):
mx = max(r,g,b); mn = min(r,g,b); diff = mx - mn
v = mx
s = 0 if mx == 0 else int(255 * diff / mx)
if diff == 0: h = 0
else:
if mx == r: h = (60 * (g - b) // diff) % 360
elif mx == g: h = (60 * (b - r) // diff) + 120
else: h = (60 * (r - g) // diff) + 240
return h, s, v
def hsv_to_rgb_i(h,s,v):
if s == 0:
return v, v, v
s = s/255.0; v = v/255.0
hi = int(h/60) % 6; f = (h/60) - int(h/60)
p = v*(1-s); q = v*(1-s*f); t = v*(1-(1-f)*s)
if hi==0: r,g,b = v,t,p
elif hi==1: r,g,b = q,v,p
elif hi==2: r,g,b = p,v,t
elif hi==3: r,g,b = p,q,v
elif hi==4: r,g,b = t,p,v
else: r,g,b = v,p,q
return int(r*255+0.5), int(g*255+0.5), int(b*255+0.5)
def apply_hsv_bgra_block(bytes_in, w, h, hue_shift_deg=0, sat_pct=100, val_pct=100):
data = bytearray(bytes_in); mv = memoryview(data)
for i in range(0, len(mv), 4):
b,g,r,a = mv[i], mv[i+1], mv[i+2], mv[i+3]
h,s,v = rgb_to_hsv_i(r,g,b)
h = (h + hue_shift_deg) % 360
s = int(s * (sat_pct/100.0)); s = 0 if s<0 else 255 if s>255 else s
v = int(v * (val_pct/100.0)); v = 0 if v<0 else 255 if v>255 else v
r2,g2,b2 = hsv_to_rgb_i(h,s,v)
mv[i], mv[i+1], mv[i+2], mv[i+3] = b2, g2, r2, a
return data
4.6. 3.6 Gradient Map (luminance → barva)
Držte 2–5 uzlů: pozice t∈[0,1]
, barva R,G,B (0..255)
; lineární interpolace.
def lerp(a,b,t): return int(a + (b-a)*t + 0.5)
def gradient_color(stops, t):
# stops: list[(t, (r,g,b))], t v asc. pořadí
if t <= stops[0][0]: return stops[0][1]
if t >= stops[-1][0]: return stops[-1][1]
for i in range(len(stops)-1):
t0,c0 = stops[i]; t1,c1 = stops[i+1]
if t0 <= t <= t1:
u = (t - t0) / max(1e-9, (t1 - t0))
r = lerp(c0[0], c1[0], u)
g = lerp(c0[1], c1[1], u)
b = lerp(c0[2], c1[2], u)
return (r,g,b)
return stops[-1][1]
def apply_gradient_map_bgra_block(bytes_in, w, h, stops, gray_mode="709"):
data = bytearray(bytes_in); mv = memoryview(data)
for i in range(0, len(mv), 4):
b,g,r,a = mv[i], mv[i+1], mv[i+2], mv[i+3]
y = gray_pixel(r,g,b, gray_mode) / 255.0
r2,g2,b2 = gradient_color(stops, y)
mv[i], mv[i+1], mv[i+2], mv[i+3] = b2, g2, r2, a
return data
5. Preview & Apply workflow
- Preview: získejte ROI → downsample (např. delší strana max 512 px) → aplikujte aktivní bloky (v doporučeném pořadí) → zobrazte v
QLabel
. - Apply: načtěte plné ROI → aplikujte aktivní bloky (stejná konfigurace) →
setPixelData(…)
→doc.refreshProjection()
. - Apply on copy: duplikujte
node
(nebo přidejte novou vrstvu) a efekt proveďte tam.
6. Odevzdání
- Adresář ColorLab (
.desktop
,init.py
,color_lab.py
), případněgrad_presets.json
. - README: popis voleb, pořadí pipeline, limity (8bit přesnost, bez lineárního prostoru), screenshoty dialogu.
- Ukázky PNG: grayscale metody, sepia (0/50/100 %), curves (S-shape), HSL (±saturace), Gradient Map (duotone).
7. Checklist kvality
- BGRA pořadí, A beze změny.
- Preview rychlé, nezasahuje do zdroje (downsample).
- Apply respektuje ROI dle výběru; bez výběru celé plátno.
- Curves/Levels přes LUT 256 (Master; volitelně R/G/B).
- HSL/HSV funguje (Hue shift ±180°, sat/value %).
- Sepia přes 3×3 matici + Strength (mix).
- Gradient Map interpoluje 2–5 uzlů správně (linear).
-
refreshProjection()
po zápisu, bez pádů.
8. Hodnocení (rubrika 0–4)
- Funkčnost (0–4): správné výsledky všech bloků; mix/pořadí pipeline.
- Výkon (0–4): plynulé preview, rychlé Apply na 2–4k ROI.
- Kód (0–4): čisté oddělení UI/logic, LUTy, komentáře.
- UX (0–4): srozumitelné názvy, bezpečné defaulty, Reset/Apply on copy.
9. Rozšíření (bonus)
- Per-channel curves (R/G/B nezávisle) + přepínání zobrazení histogramu ROI.
- Levels s černým/bílým bodem (auto na percentily, např. 1 % a 99 %).
- Dithering při silném „posterize“ (Floyd–Steinberg).
- Linear workflow: volitelný převod do lineárního prostoru (gamma 2.2 approximace) → aplikace křivek → zpět.
10. Troubleshooting
- Změněná průhlednost — omylem měníte A; A vždy kopírujte.
- „Zubaté“ křivky — chybná LUT nebo body mimo 0..255; zkontrolujte klipy a monotónnost (volitelně vynucujte).
- Divné barvy po HSL — hlídejte hranice
s,v
(0..255), správný wraph∈[0,360)
. - Pomalé preview — downsample, debounce; slučujte bloky (např. curves + sepia v jednom průchodu).
11. Kostra pluginu (zkráceno)
# color_lab.py
from krita import Extension
from PyQt5.QtWidgets import QDialog, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QSlider, QColorDialog
class ColorLab(Extension):
def __init__(self, parent):
super().__init__(parent)
self.app = parent
def setup(self): pass
def createActions(self, window):
act = window.createAction("pga_color_lab", "ColorLab (Curves/HSL/Sepia/Gradient)", "tools/scripts")
act.triggered.connect(self.show_dialog)
def show_dialog(self):
dlg = ColorLabDialog(self.app)
dlg.exec_()
class ColorLabDialog(QDialog):
def __init__(self, app, parent=None):
super().__init__(parent)
self.app = app
self.setWindowTitle("ColorLab")
# TODO: vytvořit taby + ovládací prvky (viz sekce UI)
# Přidat Preview/Apply/Apply on copy/Reset
# Na Preview: zisk ROI→downsample→apply_pipeline(preview=True)→zobrazit QLabel
# Na Apply: zisk ROI (plná velikost)→apply_pipeline(preview=False)→setPixelData→refresh
# Pipeline: curves->HSL->sepia->gradient map
# (viz implementační bloky výše)
# ...
Důležité:
Stabilita & výkon:
Dělejte co nejméně průchodů přes data. Ideál: sloučit vybrané bloky do jednoho průchodu tam, kde to jde (např. LUT curves + HSL + sepia mix v jedné smyčce).
U velkých ROI ukažte QProgressDialog a v cyklu občas volejte QApplication.processEvents()
.
PGA — Krita Python Plugins: Cvičení 6 (BatchLab — hromadný export vrstev, sprite-sheet, balení a distribuce)
Autor: tým PGA :toc: macro
ZIP :toclevels: 3 :sectnums: :icons: font :source-highlighter: rouge :experimental: :lang: cs
Cíl cvičení: dokončit semestrální část tvorbou plně použitelných nástrojů pro automatizovaný export:
1) Batch export vrstev/skupin (více formátů, škálování, ořez průhlednosti, šablony názvů).
2) Sprite-sheet builder (mřížka i „tight“ ořez s paddingem + JSON/CSV manifest).
3) Release pluginu: metadata, ikona, README
, krita-pip závislosti, jednoduché logování a nastavení.
1. Předpoklady
- Umíte: číst/zapisovat pixely, respektovat BGRA, pracovat s ROI a výběrem.
- Znáte:
.desktop
+init.py
+createActions()
+ PyQt dialogy. - Doporučeno: projít Cvičení 2–5 (konvoluce, FFT, ColorLab).
2. Zadání (souhrn)
Vytvořte plugin BatchLab se dvěma hlavními kartami:
- A) Batch Export
- Scope: Selected Layers, Visible Layers, Top-level Groups, All.
- Formáty: PNG (alfa), WebP, JPEG, TIFF (8/16-bit), volba kvality/komprese.
- Škálování: 1× / 2× / 0.5× / vlastní procenta; volitelné přepočítat DPI.
- Ořez průhlednosti („trim“): detekce bbox podle A kanálu; možnost přidat padding (px).
- Šablona názvu souborů: např.
{doc}_{layer}_{v:03d}@{scale}x.{ext}
(viz níže). - Cíl exportu: výběr adresáře, volitelně
create subfolder per doc
.
- B) Sprite-Sheet
- Vstup: kolekce vrstev (nebo PNG v adresáři).
- Layout: Grid (N×M) nebo Tight pack (řazení po řádcích, fixní dlaždice).
- Tile size:
auto
(max(bbox_i)) nebo pevněW×H
. - Padding: mezery (px) kolem dlaždic; volitelně extrude 1px (duplikace okraje pro lepší filtrování).
- Výstup: atlas PNG + manifest (JSON/CSV) s pozicemi (
x,y,w,h,name,anchor
). - Volitelně: Power-of-Two canvas (1024, 2048…).
- Common
- Preview (rychlá suchá simulace rozložení/názvů).
- Run s
QProgressDialog
; Cancel (bezpečné přerušení). - Log panel (stručné info, chyby).
- Settings persist (QSettings) – poslední cesta, formát, šablona, atd.
3. UI návrh
- Hlavní
QTabWidget
se záložkami Batch Export a Sprite-Sheet. - Vpravo dole:
Preview
,Run
,Close
. Vlevo dole:Open Output Folder
. - Dole přes celou šířku:
QPlainTextEdit
(„Log“), readonly, auto-scroll. - „i“ ikony s tooltipy u voleb (kvalita WebP/JPEG, význam paddingu apod.).
4. Export – šablona názvu souboru
Doporučené placeholdery:
Placeholder | Popis |
---|---|
{doc} | Jméno dokumentu bez přípony |
{layer} | Jméno vrstvy/skupiny (sanitizované) |
{idx} | Pořadí v rámci exportu (1-based) |
{scale} | Měřítko (např. 1.0, 2.0) |
{v:03d} | Verze podle čísla v názvu dokumentu (detekce _v001 ) |
{ext} | Přípona podle zvoleného formátu (png , webp , …) |
Příklad:
{doc}_{layer}_{v:03d}@{scale}x.{ext}
→ ui_icons_play_v007@2.0x.png
5. Práce s vrstvami a bbox (ořez podle A kanálu)
5.1. 4.1 Vyčtení projekce vrstvy a bbox
from krita import Krita
from PyQt5.QtGui import QImage
from PyQt5.QtCore import QRect
def node_projection_qimage(doc, node):
# Získejte bounds vrstvy v dokumentu (globální souřadnice):
b = node.bounds()
x, y, w, h = b.x(), b.y(), b.width(), b.height()
if w <= 0 or h <= 0:
return None, QRect()
# Získejte BGRA byty dané vrstvy (projekce včetně efektů):
data = node.projectionPixelData(x, y, w, h)
img = QImage(data, w, h, QImage.Format_ARGB32) # BGRA↔ARGB32 byte-order
return img.copy(), QRect(x, y, w, h)
def alpha_trim_bbox(qimg: QImage, threshold=1):
# Najde minimální obdélník s alfou > threshold
w, h = qimg.width(), qimg.height()
if w==0 or h==0: return QRect(0,0,0,0)
ptr = qimg.bits(); ptr.setsize(h*qimg.bytesPerLine())
import numpy as np
a = np.frombuffer(ptr, dtype=np.uint8).reshape(h, qimg.bytesPerLine())[:, 3::4] # alfa kanál
ys, xs = np.where(a > threshold)
if xs.size == 0 or ys.size == 0:
return QRect(0,0,0,0)
x0, x1 = int(xs.min()), int(xs.max())
y0, y1 = int(ys.min()), int(ys.max())
return QRect(x0, y0, x1 - x0 + 1, y1 - y0 + 1)
Upozornění:
projectionPixelData()
vrací projekci (vč. efektů). Pokud chcete čistě obsah vrstvy bez efektů, použijte pixelData()
.
Pozor na výkon u velmi velkých vrstev — u Batch Exportu preferujte export menších izolovaných vrstev oproti celé projekci plátna.
5.2. 4.2 Škálování, padding a uložení
from PyQt5.QtGui import QImage, QPainter
from PyQt5.QtCore import Qt
def pad_qimage(qimg: QImage, pad: int, color=(0,0,0,0)):
if pad <= 0: return qimg
w, h = qimg.width()+2*pad, qimg.height()+2*pad
out = QImage(w, h, QImage.Format_ARGB32)
out.fill(Qt.transparent)
p = QPainter(out)
p.drawImage(pad, pad, qimg)
p.end()
return out
def scale_qimage(qimg: QImage, scale: float):
if abs(scale - 1.0) < 1e-6: return qimg
w = max(1, int(round(qimg.width()*scale)))
h = max(1, int(round(qimg.height()*scale)))
# u ikon/UI preferujeme nejbližšího souseda:
return qimg.scaled(w, h, Qt.IgnoreAspectRatio, Qt.FastTransformation)
5.3. 4.3 Export přes Krita API vs. Qt
- Krita
exportImage(path, InfoObject)
– dobré pro celou projekci dokumentu (závisí na viditelnosti vrstev). - Qt
QImage.save(path)
– když si sami připravíte vyříznutý a škálovanýQImage
z projekce vrstvy.
6. Batch Export – algoritmus
- Vyberte uzly podle „Scope“ (např. jen top-level layers v aktuální skupině).
- Pro každý uzel:
- Získejte projekci + bbox; aplikujte trim a padding.
- Aplikujte scale (1×, 2×, …).
- Vygenerujte název souboru dle šablony (sanitizovat
/\:*?"<>|
). - Uložte
QImage.save()
(PNG/WebP/JPEG/TIFF) s nastavenou kvalitou/kompresí. - Log:
OK
nebo chybová zpráva.
- Po dokončení: otevřete výstupní složku (volitelně).
Důležité:
PNG/WebP podporují alfa a hodí se pro UI/hry. JPEG nepodporuje průhlednost (zvažte volbu pozadí a flatten). TIFF můžete exportovat i 16-bit – hodí se pro tisk/archiv.
7. Sprite-Sheet – algoritmus (Grid / Tight pack)
7.1. 6.1 Grid mód
- Zadejte
cols × rows
nebo automaticky spočítejte z počtu snímků. tileW × tileH
= buď auto (max bboxů) nebo fixní.- Výsledný canvas =
cols*(tileW+2*pad)
×rows*(tileH+2*pad)
(plus extrude okrajů, pokud zapnete). - Každý snímek zarovnejte do dlaždice vlevo nahoře (nebo na střed) a uložte pozici do manifestu.
7.2. 6.2 Tight pack (jednoduché řádkování)
- Seřaďte snímky podle šířky/součtu plochy, přidávejte je po řádcích, sledujte max výšku řádku.
- Po ukončení řádku přidejte row padding.
- Volitelně dorovnejte plátno na power-of-two.
7.3. 6.3 Manifest (JSON)
{
"image": "atlas.png",
"tileWidth": 64,
"tileHeight": 64,
"padding": 2,
"frames": [
{"name":"walk_000","x":0,"y":0,"w":64,"h":64,"anchor":[32,48]},
{"name":"walk_001","x":66,"y":0,"w":64,"h":64,"anchor":[32,48]}
]
}
8. Kostra pluginu (zkráceně)
# batch_lab.py
from krita import Extension, Krita
from PyQt5.QtWidgets import (QDialog, QTabWidget, QWidget, QFileDialog, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QSpinBox, QDoubleSpinBox, QCheckBox,
QComboBox, QPlainTextEdit, QApplication, QProgressDialog)
from PyQt5.QtCore import Qt
import os, json, re
SAFE = re.compile(r'[^A-Za-z0-9._@\-+]')
class BatchLab(Extension):
def __init__(self, parent): super().__init__(parent); self.app = parent
def setup(self): pass
def createActions(self, window):
act = window.createAction("pga_batch_lab", "BatchLab (Export & SpriteSheet)", "tools/scripts")
act.triggered.connect(self.show_dialog)
def show_dialog(self):
dlg = BatchLabDialog(self.app); dlg.exec_()
class BatchLabDialog(QDialog):
def __init__(self, app, parent=None):
super().__init__(parent); self.app = app
self.setWindowTitle("BatchLab — Export & SpriteSheet")
self.tabs = QTabWidget(self)
self.tab_export = QWidget(); self.tab_sheet = QWidget()
self.tabs.addTab(self.tab_export, "Batch Export")
self.tabs.addTab(self.tab_sheet, "Sprite-Sheet")
# ... zde vytvořte ovládací prvky (scope, formát, scale, trim, padding, template, destFolder ...)
# ... a u druhé karty (grid/tight, tileW/H, padding, pot, manifest path)
self.log = QPlainTextEdit(); self.log.setReadOnly(True)
btnPreview = QPushButton("Preview"); btnRun = QPushButton("Run"); btnClose = QPushButton("Close")
btnPreview.clicked.connect(self.on_preview); btnRun.clicked.connect(self.on_run); btnClose.clicked.connect(self.close)
lay = QVBoxLayout(self); lay.addWidget(self.tabs); lay.addWidget(self.log)
row = QHBoxLayout(); row.addStretch(1); row.addWidget(btnPreview); row.addWidget(btnRun); row.addWidget(btnClose)
lay.addLayout(row)
# TODO: načíst QSettings (poslední cesta apod.)
def logln(self, msg): self.log.appendPlainText(msg)
def on_preview(self):
# suchý průchod: spočítat názvy souborů / layout atlasu, nic neukládat
self.logln("[Preview] ...")
def on_run(self):
doc = self.app.activeDocument()
if not doc: self.logln("Žádný aktivní dokument."); return
# připravit uzly dle scope, progress dialog, smyčka přes snímky/vrstvy
# pro každý: projekce -> trim -> padding -> scale -> save -> záznam do JSON (u atlasu)
self.logln("Hotovo.")
9. Ukládání nastavení a verze
- Použijte QSettings (klíč „pga/batchlab/…“).
- Nezapomeňte uložit template string, poslední dest folder, volby trim/scale/format.
- Do
README
přidejte Release notes (verze, změny).
10. Balení a distribuce
- Struktura:
my_batch_lab/ my_batch_lab.desktop __init__.py batch_lab.py icons/ (32/64px PNG) README.md LICENSE
.desktop
:
[Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=my_batch_lab X-Krita-Manual=Manual.html Name=BatchLab (Export & SpriteSheet) Comment=Batch export layers and build sprite sheets
- Závislosti: pokud potřebujete externí balíčky (např.
numpy
), použijte krita-pip (viz cvičení 1 – Imports). U BatchLab by měl stačit čistý PyQt/Krita API. - Ikona: umístěte 32/64 px do
icons/
a načtěte v UI (není povinné).
11. Výkon a stabilita (tipy)
- Data-driven smyčky: minimalizujte počet konverzí → pracujte co nejdéle v
QImage
. - Trim + padding dělejte před škálováním (rychlejší, přesnější).
- Názvy souborů sanitizujte (viz
SAFE
regex); u dlouhých jmen zkraťte (např. 80 znaků). - Zámky souborů / přepsání: kontrolujte existenci; nabídněte „overwrite / skip / rename“.
- Cancel v
QProgressDialog
: po stisku okamžitě a bezpečně ukončete cyklus. - Power-of-Two: u atlasů pro některé enginy povinné → nabízí smysluplné volby (1024, 2048…).
- Extrude okraje: duplikujte 1px rám (lepší bilineární/atlasové filtrování ve hře).
12. Odevzdání
- Adresář pluginu my_batch_lab se zdrojáky (desktop, init, py, icons).
- GIF/PNG ukázky:
- Batch export – 3 ukázkové vrstvy před/po (trim+scale).
- Sprite-sheet – atlas PNG + manifest JSON (2-3 řádky).
- README s instalací, popisem voleb, známými limity, „Jak reportovat bug“.
- (Volitelně) Manual.html – krátký tutoriál se screenshoty.
13. Checklist kvality
- Export funguje pro Selected / Visible / All.
- Škálování a trim se uplatní, padding a template generuje správné názvy.
- Sprite-sheet: Grid + Tight pack, správné
x,y,w,h
v manifestu. -
QProgressDialog
+ Cancel; UI nepadá, běh je logován. - Nastavení se pamatuje (QSettings).
- Kód oddělený: IO / UI / layout / util funkce, komentáře.
-
README
+.desktop
+ ikona; plugin se načte na Win/macOS/Linux.
14. Hodnocení (rubrika 0–4)
- Funkčnost (0–4): export + atlas + manifest zcela funkční; korektní pojmenování.
- UX (0–4): přehledné UI, srozumitelné volby, log, persist nastavení.
- Výkon (0–4): plynulý běh na 100+ snímcích, rozumné využití paměti.
- Kvalita kódu (0–4): struktura, komentáře, ošetření chyb, křížová kompatibilita.
15. Troubleshooting
- Prázdný výstup / černé PNG: špatný bbox nebo nulová alfa → vypněte trim, zkontrolujte
projectionPixelData
. - Rozmazané ikony po škálování: pro UI používejte
Qt.FastTransformation
(nearest), pro fotkySmoothTransformation
. - Špatné názvy souborů na Windows: znaky
<>:"/\|?*
→ sanitizovat, nahradit_
. - Rozhozený atlas: zkontrolujte stejné
tileW/H
v Grid módu, čiže auto bere max bbox. - Velké atlasy se nevejdou: zapněte power-of-two nebo zvyšte rozměr; nebo rozdělte do více atlasů.
16. Bonusy (rozšíření)
- Více formátů naráz (PNG @1×, @2× + WebP).
- Šablony presetů exportu (uložit/načíst JSON s konfigurací).
- Per-layer overrides (např. custom anchor/metadata v názvu vrstvy
#anchor=16,24
). - DLL/Worker vlákno pro ukládání (QtConcurrent) – neblokuje UI.
17. Použití AI, citace a akademická integrita
AI nástroje (ChatGPT, generátory) jsou povoleny pro nápovědu a boilerplate. Zakázáno: odevzdat generovaný obsah bez vlastní práce, nebo použití cizích děl bez licence. Citace: U cizích zdrojů uvádějte autory/licence v README. Projekty s lidmi/brandem: Zvláštní pozornost k právům na podobu/loga.
18. Tahák zkratek (Krita)
Akce | Zkratka (Win/macOS) |
---|---|
Zvětšit/Zmenšit štětec | [ / ] |
Zrcadlení pohledu | M |
Rychlá maska | Q |
Kapátko | Ctrl + Alt + klik / ⌘ + ⌥ + klik |
Rychlý posun plátna | MMB drag / ⌥ + Space + drag |
Rotace/Reset plátna | Shift + Space + drag / 5 |
Přepínání guma/štětec | E |
Skupiny vrstev – viditelnost/lock | Shift + klik na oko / zámek |
19. Glosář pojmů (Krita/Blender/PGA)
- BGRA
- Byte-pořadí kanálů v Krita API (B,G,R,A). Alfa vždy kopírujte 1:1.
- ROI
- Region of Interest – oblast výběru. Preview běží na downsamplu ROI, Apply na plné ROI.
- LUT
- Look-Up Table (256 prvků/kanál) pro rychlé křivky/úrovně.
- Hann okno
- Okno pro FFT, tlumí zvonění po ostrých ořezech ve spektru.
- Onion-skin
- Průhledné zobrazení předchozích/následujících snímků v animaci.
- PoT (Power-of-Two)
- Velikost textury 2^n (1024, 2048…) – někdy vyžaduje engine.
- Tight pack
- Atlas skládající dlaždice s proměnlivou velikostí + manifest s pozicemi.