This commit is contained in:
2026-07-09 02:59:53 +03:00
commit 7ace048d94
817 changed files with 234289 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os
import subprocess
import glob
TASKS = [
("_tmp/kra_tunes_simple_black/drop_shadow_kra_set", "_tmp/kra_tunes_simple_black/drop_shadow_png_kra_set"),
("_tmp/kra_tunes_simple_red/drop_shadow_kra_set", "_tmp/kra_tunes_simple_red/drop_shadow_png_kra_set"),
("_tmp/kra_tunes_simple_black/outer_glow_kra_set", "_tmp/kra_tunes_simple_black/outer_glow_png_kra_set"),
("_tmp/kra_tunes_simple_red/outer_glow_kra_set", "_tmp/kra_tunes_simple_red/outer_glow_png_kra_set"),
("_tmp/kra_tunes_simple_black/inner_glow_kra_set", "_tmp/kra_tunes_simple_black/inner_glow_png_kra_set"),
("_tmp/kra_tunes_simple_red/inner_glow_kra_set", "_tmp/kra_tunes_simple_red/inner_glow_png_kra_set"),
("_tmp/kra_tunes_simple_black/bevel_emboss_kra_set", "_tmp/kra_tunes_simple_black/bevel_emboss_png_kra_set"),
("_tmp/kra_tunes_simple_red/bevel_emboss_kra_set", "_tmp/kra_tunes_simple_red/bevel_emboss_png_kra_set"),
]
def main():
# Check if Krita exists
try:
subprocess.run(["krita", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
print("Error: 'krita' executable not found in your PATH.")
print("Please install Krita or run this script in an environment where Krita is available.")
return
for kra_dir, png_dir in TASKS:
if not os.path.exists(kra_dir):
print(f"Directory {kra_dir} does not exist. Skipping.")
continue
kra_files = glob.glob(os.path.join(kra_dir, "*.kra"))
if not kra_files:
print(f"No .kra files found in {kra_dir}. Skipping.")
continue
os.makedirs(png_dir, exist_ok=True)
print(f"Found {len(kra_files)} KRA files in {kra_dir}. Exporting to {png_dir}...")
count = 0
for kra_path in kra_files:
filename = os.path.basename(kra_path)
png_name = filename.replace(".kra", ".png")
png_path = os.path.join(png_dir, png_name)
if os.path.exists(png_path):
continue
print(f" Exporting {filename} -> {png_name}...")
cmd = ["krita", kra_path, "--export", "--export-filename", png_path]
success = False
for attempt in range(2):
try:
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10)
if res.returncode == 0:
count += 1
success = True
break
else:
print(f" FAILED to export {filename}: {res.stderr.decode('utf-8', 'ignore')}")
break
except subprocess.TimeoutExpired:
print(f" TIMEOUT on {filename} (attempt {attempt+1}), retrying...")
# Kill any lingering krita processes just in case
os.system("killall -9 krita 2>/dev/null")
if not success:
print(f" Skipping {filename} after failure/timeout.")
print(f"Done with {kra_dir}. Exported {count} new files.")
if __name__ == "__main__":
main()
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import os
import subprocess
import glob
KRA_DIR = "_tmp/drop_shadow_kra_set"
PNG_DIR = "_tmp/drop_shadow_png_kra_set"
def main():
kra_files = glob.glob(os.path.join(KRA_DIR, "*.kra"))
if not kra_files:
print(f"No .kra files found in {KRA_DIR}. Run generate_kra_set.py first.")
return
os.makedirs(PNG_DIR, exist_ok=True)
print(f"Found {len(kra_files)} KRA files. Beginning headless export via Krita CLI...")
# Check if Krita exists
try:
subprocess.run(["krita", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
print("Error: 'krita' executable not found in your PATH.")
print("Please install Krita or run this script in an environment where Krita is available.")
return
count = 0
for kra_path in kra_files:
filename = os.path.basename(kra_path)
png_name = filename.replace(".kra", ".png")
png_path = os.path.join(PNG_DIR, png_name)
if os.path.exists(png_path):
continue
print(f"Exporting {filename} -> {png_name}...")
# Krita headless export command
cmd = ["krita", kra_path, "--export", "--export-filename", png_path]
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if res.returncode == 0:
count += 1
else:
print(f" FAILED to export {filename}: {res.stderr.decode()}")
print(f"Done. Successfully exported {count} files to {PNG_DIR}.")
if __name__ == "__main__":
main()
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
import os
import struct
import zipfile
import xml.etree.ElementTree as ET
# Offsets for BevelEmboss style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
# lagl (local angle): 747
# Lald (local altitude): 768
# srgR (depth): 790
# blur (size): 819
# bvlD (direction enum value, e.g. b"In " or b"Out "): 851
# Sftn (soften): 1094
# hglO (highlight opacity): 512
# sdwO (shadow opacity): 652
# hglC (highlight R/G/B doubles): 438, 458, 478
# sdwC (shadow R/G/B doubles): 590, 610, 630
# hglM (highlight blend mode classID): 402
# sdwM (shadow blend mode classID): 544
BASE_KRA = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra"
OUT_DIR = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/bevel_emboss_kra_set"
def get_double_bytes(val):
return struct.pack(">d", float(val))
def main():
bases = [
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/bevel_emboss_kra_set"),
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/bevel_emboss_kra_set")
]
for base_kra, out_dir in bases:
if not os.path.exists(base_kra):
print(f"Error: Base Krita file {base_kra} not found.")
continue
os.makedirs(out_dir, exist_ok=True)
# Read base archive and cache entries in memory
base_zip = zipfile.ZipFile(base_kra, 'r')
files_cache = {}
for name in base_zip.namelist():
files_cache[name] = base_zip.read(name)
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
if asl_path_in_zip in files_cache:
asl_data = bytearray(files_cache[asl_path_in_zip])
else:
fallback_paths = [
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
"_images/kra_style_test/sultan.kra",
"_images/_psd_stil_test/sultan_test/sultan.kra"
]
sultan_zip = None
for path in fallback_paths:
if os.path.exists(path):
sultan_zip = zipfile.ZipFile(path, 'r')
break
if sultan_zip is None:
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
return
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
sultan_zip.close()
xml_data = files_cache["maindoc.xml"]
# Set visibility in XML: both target layer and Background should be visible
# Also ensure the layerstyle attribute is attached to the target layer
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
root = ET.fromstring(xml_data)
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
if layer.attrib.get('name') in ['Layer 2', 'Red Rectangle']:
layer.set('visible', '1')
layer.set('layerstyle', '{4bbe8f2c-7ad2-4e31-8af0-9ad6715cacf4}')
elif layer.attrib.get('name') == 'Background':
layer.set('visible', '1')
else:
layer.set('visible', '0')
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
# Parameter lists
sizes = [5.0, 10.0, 20.0, 30.0, 50.0]
softens = [0.0, 4.0, 8.0, 16.0]
depths = [25.0, 50.0, 75.0, 100.0]
angles = [90.0, 120.0]
directions = [
(b"In ", "up"),
(b"Out ", "down"),
]
total = len(sizes) * len(softens) * len(depths) * len(angles) * len(directions)
print(f"Generating {total} styled KRA files for Bevel & Emboss from {os.path.basename(base_kra)}...")
def patch_untf(asl, key, value):
idx = asl.find(key)
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
asl[idx+12:idx+20] = get_double_bytes(value)
def patch_enum(asl, key, value):
idx = asl.find(key)
if idx != -1 and asl[idx+4:idx+8] == b'enum':
asl[idx+20:idx+24] = value
count = 0
for size in sizes:
for soften in softens:
for depth in depths:
for angle in angles:
for dir_bytes, dir_name in directions:
count += 1
filename = f"be_s{int(size)}_sf{int(soften)}_d{int(depth)}_a{int(angle)}_{dir_name}.kra"
kra_path = os.path.join(out_dir, filename)
# Clone and patch ASL
patched_asl = bytearray(asl_data)
patch_untf(patched_asl, b'blur', size)
patch_untf(patched_asl, b'Sftn', soften)
patch_untf(patched_asl, b'srgR', depth)
patch_untf(patched_asl, b'lagl', angle)
patch_enum(patched_asl, b'bvlD', dir_bytes)
# Write new ZIP archive
with zipfile.ZipFile(kra_path, 'w') as out_zip:
has_asl = False
for item in base_zip.infolist():
if item.filename == "Unnamed/annotations/layerstyles.asl":
out_zip.writestr(item, patched_asl)
has_asl = True
elif item.filename == "maindoc.xml":
out_zip.writestr(item, xml_patched)
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
else:
out_zip.writestr(item, files_cache[item.filename])
if not has_asl:
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
if count % 10 == 0:
print(f" [{count}/{total}] generated...")
base_zip.close()
print(f"Successfully generated {count} files under {out_dir}.")
if __name__ == "__main__":
main()
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
import os
import struct
import zipfile
import xml.etree.ElementTree as ET
# Offsets for InnerGlow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
# Md (blend mode): 1510
# Clr (color) RGBC double offsets:
# R double: 1556
# G double: 1576
# B double: 1596
# Opct (opacity): 1620
# Ckmt (choke/spread): 1672
# blur (size): 1696
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
OUT_DIR = "_tmp/inner_glow_kra_set"
def get_double_bytes(val):
return struct.pack(">d", float(val))
def main():
bases = [
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/inner_glow_kra_set"),
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/inner_glow_kra_set")
]
for base_kra, out_dir in bases:
if not os.path.exists(base_kra):
print(f"Error: Base Krita file {base_kra} not found.")
continue
os.makedirs(out_dir, exist_ok=True)
# Read base archive and cache entries in memory
base_zip = zipfile.ZipFile(base_kra, 'r')
files_cache = {}
for name in base_zip.namelist():
files_cache[name] = base_zip.read(name)
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
if asl_path_in_zip in files_cache:
asl_data = bytearray(files_cache[asl_path_in_zip])
else:
fallback_paths = [
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
"_images/kra_style_test/sultan.kra",
"_images/_psd_stil_test/sultan_test/sultan.kra"
]
sultan_zip = None
for path in fallback_paths:
if os.path.exists(path):
sultan_zip = zipfile.ZipFile(path, 'r')
break
if sultan_zip is None:
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
return
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
sultan_zip.close()
xml_data = files_cache["maindoc.xml"]
# Set visibility in XML: both target layer and Background should be visible
# Also ensure the layerstyle attribute is attached to the target layer
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
root = ET.fromstring(xml_data)
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
if layer.attrib.get('name') in ['Layer 3', 'Red Rectangle']:
layer.set('visible', '1')
layer.set('layerstyle', '{117f8514-88e4-4553-bccb-44a87b6b9182}')
elif layer.attrib.get('name') == 'Background':
layer.set('visible', '1')
else:
layer.set('visible', '0')
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
# Parameter lists
chokes = [0.0, 20.0]
sizes = [10.0, 30.0]
opacities = [0.75, 1.0]
blend_modes = [
(b"Nrml", "normal"),
(b"Scrn", "screen"),
(b"LDdg", "lineardodge"),
]
glow_colors = [
([220, 30, 30, 255], "red"),
([30, 180, 30, 255], "green"),
]
total = len(chokes) * len(sizes) * len(opacities) * len(blend_modes) * len(glow_colors)
print(f"Generating {total} styled KRA files for Inner Glow from {os.path.basename(base_kra)}...")
def patch_untf(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
asl[idx+12:idx+20] = get_double_bytes(value)
def patch_enum(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'enum':
asl[idx+20:idx+24] = value
def patch_doub(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'doub':
asl[idx+8:idx+16] = get_double_bytes(value)
count = 0
for choke in chokes:
for size in sizes:
for opacity in opacities:
for bm_bytes, bm_name in blend_modes:
for color, color_name in glow_colors:
count += 1
filename = f"ig_sp{int(choke)}_s{int(size)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
kra_path = os.path.join(out_dir, filename)
# Clone and patch ASL
patched_asl = bytearray(asl_data)
start_idx = patched_asl.find(b'IrGl')
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
patch_untf(patched_asl, start_idx, b'Ckmt', choke)
patch_untf(patched_asl, start_idx, b'blur', size)
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
# Color: find Clr after start_idx, then Rd , Grn , Bl
clr_idx = patched_asl.find(b'Clr ', start_idx)
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
# Write new ZIP archive
with zipfile.ZipFile(kra_path, 'w') as out_zip:
has_asl = False
for item in base_zip.infolist():
if item.filename == "Unnamed/annotations/layerstyles.asl":
out_zip.writestr(item, patched_asl)
has_asl = True
elif item.filename == "maindoc.xml":
out_zip.writestr(item, xml_patched)
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
else:
out_zip.writestr(item, files_cache[item.filename])
if not has_asl:
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
if count % 20 == 0:
print(f" [{count}/{total}] generated...")
base_zip.close()
print(f"Successfully generated {count} files under {out_dir}.")
if __name__ == "__main__":
main()
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env python3
import os
import struct
import zipfile
import xml.etree.ElementTree as ET
# Offsets for DropShadow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
# Opct (opacity): 2504
# lagl (local angle): 2541
# Dstn (distance): 2565
# Ckmt (choke/spread): 2589
# blur (size): 2613
# Md (blend mode value id): 2406
# Clr (color) RGBC double offsets:
# R double: 2448
# G double: 2468
# B double: 2488
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
OUT_DIR = "_tmp/drop_shadow_kra_set"
def get_double_bytes(val):
return struct.pack(">d", float(val))
def main():
bases = [
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/drop_shadow_kra_set"),
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/drop_shadow_kra_set")
]
for base_kra, out_dir in bases:
if not os.path.exists(base_kra):
print(f"Error: Base Krita file {base_kra} not found.")
continue
os.makedirs(out_dir, exist_ok=True)
# Read base archive and cache entries in memory
base_zip = zipfile.ZipFile(base_kra, 'r')
files_cache = {}
for name in base_zip.namelist():
files_cache[name] = base_zip.read(name)
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
if asl_path_in_zip in files_cache:
asl_data = bytearray(files_cache[asl_path_in_zip])
else:
fallback_paths = [
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
"_images/kra_style_test/sultan.kra",
"_images/_psd_stil_test/sultan_test/sultan.kra"
]
sultan_zip = None
for path in fallback_paths:
if os.path.exists(path):
sultan_zip = zipfile.ZipFile(path, 'r')
break
if sultan_zip is None:
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
return
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
sultan_zip.close()
xml_data = files_cache["maindoc.xml"]
# Set visibility in XML: both target layer and Background should be visible
# Also ensure the layerstyle attribute is attached to the target layer
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
root = ET.fromstring(xml_data)
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
if layer.attrib.get('name') in ['Layer 4', 'Red Rectangle']:
layer.set('visible', '1')
layer.set('layerstyle', '{7d378444-e311-41d3-b9c6-a19e2a8e5899}')
elif layer.attrib.get('name') == 'Background':
layer.set('visible', '1')
else:
layer.set('visible', '0')
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
# Parameter lists
angles = [0.0, 90.0, 180.0, 270.0]
distances = [5.0, 15.0, 30.0]
sizes = [10.0, 30.0]
spreads = [0.0, 20.0]
opacities = [0.75, 1.0]
blend_modes = [
(b"Nrml", "normal"),
(b"Mltp", "multiply"),
(b"Scrn", "screen"),
(b"LDdg", "lineardodge"),
]
shadow_colors = [
([220, 30, 30, 255], "red"),
([30, 180, 30, 255], "green"),
]
total = len(angles) * len(distances) * len(sizes) * len(spreads) * len(opacities) * len(blend_modes) * len(shadow_colors)
print(f"Generating {total} styled KRA files for Drop Shadow from {os.path.basename(base_kra)}...")
def patch_untf(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
asl[idx+12:idx+20] = get_double_bytes(value)
def patch_enum(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'enum':
asl[idx+20:idx+24] = value
def patch_doub(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'doub':
asl[idx+8:idx+16] = get_double_bytes(value)
count = 0
for angle in angles:
for distance in distances:
for size in sizes:
for spread in spreads:
for opacity in opacities:
for bm_bytes, bm_name in blend_modes:
for color, color_name in shadow_colors:
count += 1
filename = f"ds_a{int(angle)}_d{int(distance)}_s{int(size)}_sp{int(spread)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
kra_path = os.path.join(out_dir, filename)
# Clone and patch ASL
patched_asl = bytearray(asl_data)
start_idx = patched_asl.find(b'DrSh')
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
patch_untf(patched_asl, start_idx, b'lagl', angle)
patch_untf(patched_asl, start_idx, b'Dstn', distance)
patch_untf(patched_asl, start_idx, b'Ckmt', spread)
patch_untf(patched_asl, start_idx, b'blur', size)
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
# Color: find Clr after start_idx, then Rd , Grn , Bl
clr_idx = patched_asl.find(b'Clr ', start_idx)
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
# Write new ZIP archive
with zipfile.ZipFile(kra_path, 'w') as out_zip:
has_asl = False
for item in base_zip.infolist():
if item.filename == "Unnamed/annotations/layerstyles.asl":
out_zip.writestr(item, patched_asl)
has_asl = True
elif item.filename == "maindoc.xml":
out_zip.writestr(item, xml_patched)
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
else:
out_zip.writestr(item, files_cache[item.filename])
if not has_asl:
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
if count % 20 == 0:
print(f" [{count}/{total}] generated...")
base_zip.close()
print(f"Successfully generated {count} files under {out_dir}.")
if __name__ == "__main__":
main()
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
import os
import struct
import zipfile
import xml.etree.ElementTree as ET
# Offsets for OuterGlow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
# Md (blend mode): 3278
# Clr (color) RGBC double offsets:
# R double: 3324
# G double: 3344
# B double: 3364
# Opct (opacity): 3388
# Ckmt (choke/spread): 3440
# blur (size): 3464
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
OUT_DIR = "_tmp/outer_glow_kra_set"
def get_double_bytes(val):
return struct.pack(">d", float(val))
def main():
bases = [
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/outer_glow_kra_set"),
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/outer_glow_kra_set")
]
for base_kra, out_dir in bases:
if not os.path.exists(base_kra):
print(f"Error: Base Krita file {base_kra} not found.")
continue
os.makedirs(out_dir, exist_ok=True)
# Read base archive and cache entries in memory
base_zip = zipfile.ZipFile(base_kra, 'r')
files_cache = {}
for name in base_zip.namelist():
files_cache[name] = base_zip.read(name)
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
if asl_path_in_zip in files_cache:
asl_data = bytearray(files_cache[asl_path_in_zip])
else:
fallback_paths = [
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
"_images/kra_style_test/sultan.kra",
"_images/_psd_stil_test/sultan_test/sultan.kra"
]
sultan_zip = None
for path in fallback_paths:
if os.path.exists(path):
sultan_zip = zipfile.ZipFile(path, 'r')
break
if sultan_zip is None:
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
return
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
sultan_zip.close()
xml_data = files_cache["maindoc.xml"]
# Set visibility in XML: both target layer and Background should be visible
# Also ensure the layerstyle attribute is attached to the target layer
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
root = ET.fromstring(xml_data)
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
if layer.attrib.get('name') in ['Layer 1', 'Red Rectangle']:
layer.set('visible', '1')
layer.set('layerstyle', '{d7a0844e-a522-4673-8c83-aa4f45e7782f}')
elif layer.attrib.get('name') == 'Background':
layer.set('visible', '1')
else:
layer.set('visible', '0')
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
# Parameter lists
spreads = [0.0, 20.0]
sizes = [10.0, 30.0]
opacities = [0.75, 1.0]
blend_modes = [
(b"Nrml", "normal"),
(b"Scrn", "screen"),
(b"LDdg", "lineardodge"),
]
glow_colors = [
([220, 30, 30, 255], "red"),
([30, 180, 30, 255], "green"),
]
total = len(spreads) * len(sizes) * len(opacities) * len(blend_modes) * len(glow_colors)
print(f"Generating {total} styled KRA files for Outer Glow from {os.path.basename(base_kra)}...")
def patch_untf(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
asl[idx+12:idx+20] = get_double_bytes(value)
def patch_enum(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'enum':
asl[idx+20:idx+24] = value
def patch_doub(asl, start_idx, key, value):
idx = asl.find(key, start_idx)
if idx != -1 and asl[idx+4:idx+8] == b'doub':
asl[idx+8:idx+16] = get_double_bytes(value)
count = 0
for spread in spreads:
for size in sizes:
for opacity in opacities:
for bm_bytes, bm_name in blend_modes:
for color, color_name in glow_colors:
count += 1
filename = f"og_sp{int(spread)}_s{int(size)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
kra_path = os.path.join(out_dir, filename)
# Clone and patch ASL
patched_asl = bytearray(asl_data)
start_idx = patched_asl.find(b'OrGl')
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
patch_untf(patched_asl, start_idx, b'Ckmt', spread)
patch_untf(patched_asl, start_idx, b'blur', size)
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
# Color: find Clr after start_idx, then Rd , Grn , Bl
clr_idx = patched_asl.find(b'Clr ', start_idx)
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
# Write new ZIP archive
with zipfile.ZipFile(kra_path, 'w') as out_zip:
has_asl = False
for item in base_zip.infolist():
if item.filename == "Unnamed/annotations/layerstyles.asl":
out_zip.writestr(item, patched_asl)
has_asl = True
elif item.filename == "maindoc.xml":
out_zip.writestr(item, xml_patched)
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
else:
out_zip.writestr(item, files_cache[item.filename])
if not has_asl:
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
if count % 20 == 0:
print(f" [{count}/{total}] generated...")
base_zip.close()
print(f"Successfully generated {count} files under {out_dir}.")
if __name__ == "__main__":
main()
+39
View File
@@ -0,0 +1,39 @@
import os
import zipfile
import glob
def main():
kra_files = glob.glob("_images/**/*.kra", recursive=True)
print(f"Found KRA files: {kra_files}")
keys = [
b"DrSh", b"IrSh", b"OrGl", b"IrGl", b"ebbl", b"FrFX", b"SoFi", b"GrFl", b"PtFl", b"Scl ", b"lagl", b"Dstn", b"blur", b"Ckmt", b"Opct", b"Md ", b"Clr ", b"Sz "
]
for kra_path in kra_files:
print(f"\n--- Analyzing {kra_path} ---")
try:
with zipfile.ZipFile(kra_path, 'r') as base_zip:
asl_paths = [name for name in base_zip.namelist() if name.endswith("layerstyles.asl")]
if not asl_paths:
print(" No layerstyles.asl found.")
continue
for asl_path in asl_paths:
asl_data = base_zip.read(asl_path)
print(f" ASL path: {asl_path} ({len(asl_data)} bytes)")
for key in keys:
pos = 0
found = []
while True:
idx = asl_data.find(key, pos)
if idx == -1:
break
found.append(idx)
pos = idx + len(key)
if found:
print(f" Key {key.decode('ascii', errors='replace')} found at offsets: {found}")
except Exception as e:
print(f" Error: {e}")
if __name__ == "__main__":
main()
+32
View File
@@ -0,0 +1,32 @@
import os
import zipfile
BASE_KRA = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra"
def main():
if not os.path.exists(BASE_KRA):
print(f"Error: {BASE_KRA} not found.")
return
with zipfile.ZipFile(BASE_KRA, 'r') as base_zip:
asl_data = base_zip.read("Unnamed/annotations/layerstyles.asl")
print(f"ASL Data Size: {len(asl_data)} bytes")
# Search for common key signatures in the ASL descriptor format
# Keys are usually 4-byte identifiers
keys = [
b"DrSh", b"IrSh", b"OrGl", b"IrGl", b"ebbl", b"FrFX", b"SoFi", b"GrFl", b"PtFl", b"Scl ", b"lagl", b"Dstn", b"blur", b"Ckmt", b"Opct", b"Md ", b"Clr ", b"Sz "
]
for key in keys:
pos = 0
found = []
while True:
idx = asl_data.find(key, pos)
if idx == -1:
break
found.append(idx)
pos = idx + len(key)
print(f"Key {key.decode('ascii', errors='replace')} found at offsets: {found}")
if __name__ == "__main__":
main()
+41
View File
@@ -0,0 +1,41 @@
# Photopea / Photoshop ExtendScript Dataset Generator
This folder contains JSX scripts to generate systematic layer-effect ground-truth images in Photopea or Photoshop.
## Files
- `generate_inner_shadow_dataset.jsx` — Short starter script for Inner Shadow.
## Usage
1. Open Photopea (https://www.photopea.com) in a browser.
2. Press `Alt+Ctrl+I` (or use `File > Automate > Script`) to open the script runner.
3. Paste the contents of the desired `.jsx` file and run it.
4. Select an output folder when prompted.
Photopea will create a new document for each parameter combination, draw a black rectangle, apply the Inner Shadow effect, hide the background, and export a transparent PNG.
## Current parameter grid (short version)
| Parameter | Values |
|---|---|
| angle | 0, 90, 180, 270 |
| distance | 5, 15, 30 |
| size | 10, 30 |
| choke | 0, 20 |
| fx opacity | 75, 100 |
| fx blend mode | Normal, Multiply |
Total images: `4 × 3 × 2 × 2 × 2 × 2 = 192`.
## Notes
- Blend mode keys use Photoshop's 4-character codes (`Nrml`, `Mltp`).
- The output PNGs have transparent backgrounds, so MAE comparisons can ignore white reference pixels if needed.
- ExtendScript error handling is basic; if a parameter combination fails, the script logs it and continues.
## Next steps
- Add more layer blend modes, fill opacity, and layer opacity variations.
- Extend to Drop Shadow, Outer Glow, Inner Glow, Stroke, Bevel & Emboss.
- Add a companion Rust generator that creates matching PSD files so the same dataset can be imported back into HCIE for MAE tuning.
@@ -0,0 +1,292 @@
/**
* generate_inner_shadow_dataset.jsx
*
* Purpose:
* Generate a systematic Inner Shadow effect dataset in Photopea.
* Uses stringID-based ActionDescriptor for maximum Photopea compatibility.
*
* Logic & Workflow:
* 1. Creates a new RGB document with transparent background.
* 2. Adds a normal raster layer and fills the center with black.
* 3. Applies one Inner Shadow effect via ActionDescriptor (stringID).
* 4. Hides the background layer.
* 5. Saves the result as a transparent PNG.
*
* Usage:
* 1. Open https://www.photopea.com in a browser.
* 2. Alt+Ctrl+I (or File > Automate > Script).
* 3. Paste this script and run.
* 4. Select output folder when prompted.
*/
var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
if (!outFolder) {
alert("No output folder selected. Exiting.");
throw new Error("No output folder");
}
var angles = [0, 90, 180, 270];
var distances = [5, 15, 30];
var sizes = [10, 30];
var chokes = [0, 20];
var fxOpacities = [75, 100];
var fxBlendModes = [
{ id: "normal", name: "normal" },
{ id: "multiply", name: "multiply" }
];
function createDocument(name, width, height) {
var doc = app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
try {
var bg = doc.artLayers[doc.artLayers.length - 1];
if (bg.isBackgroundLayer) {
bg.visible = false;
}
} catch (e) {}
return doc;
}
function addBlackRectangle(doc) {
var w = doc.width;
var h = doc.height;
if (typeof w === "object" && typeof w.value === "function") { w = w.value; }
if (typeof h === "object" && typeof h.value === "function") { h = h.value; }
w = Number(w);
h = Number(h);
var margin = Math.round(Math.min(w, h) * 0.25);
var layer = doc.artLayers.add();
layer.name = "shape";
var region = [
[margin, margin],
[w - margin, margin],
[w - margin, h - margin],
[margin, h - margin]
];
doc.selection.select(region);
var black = new SolidColor();
black.rgb.red = 0;
black.rgb.green = 0;
black.rgb.blue = 0;
doc.selection.fill(black);
doc.selection.deselect();
return layer;
}
/**
* Try applying Inner Shadow via ActionDescriptor with stringID keys.
* Returns true on success, false on failure.
*/
function applyInnerShadow(params) {
try {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
ref.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
desc.putReference(stringIDToTypeID("null"), ref);
var fxDesc = new ActionDescriptor();
var isDesc = new ActionDescriptor();
isDesc.putBoolean(stringIDToTypeID("enabled"), true);
// Blend mode
isDesc.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID(params.blendMode));
// Color
var colorDesc = new ActionDescriptor();
colorDesc.putDouble(stringIDToTypeID("red"), 0);
colorDesc.putDouble(stringIDToTypeID("grain"), 0);
colorDesc.putDouble(stringIDToTypeID("blue"), 0);
isDesc.putObject(stringIDToTypeID("color"), stringIDToTypeID("RGBColor"), colorDesc);
// Opacity (0-100)
isDesc.putInteger(stringIDToTypeID("opacity"), params.opacity);
// Angle
isDesc.putInteger(stringIDToTypeID("localLightingAngle"), params.angle);
// Global light
isDesc.putBoolean(stringIDToTypeID("useGlobalAngle"), false);
// Distance
isDesc.putInteger(stringIDToTypeID("distance"), params.distance);
// Choke
isDesc.putInteger(stringIDToTypeID("chokeMatte"), params.choke);
// Blur (size)
isDesc.putInteger(stringIDToTypeID("blur"), params.size);
// Noise
isDesc.putInteger(stringIDToTypeID("noise"), 0);
// Anti-alias
isDesc.putBoolean(stringIDToTypeID("antiAlias"), true);
// Transfer function (contour)
isDesc.putBoolean(stringIDToTypeID("transferSpecIsScaled"), false);
fxDesc.putObject(stringIDToTypeID("innerShadow"), stringIDToTypeID("innerShadow"), isDesc);
desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), fxDesc);
executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
return true;
} catch (e) {
$.writeln("stringID failed: " + e);
return false;
}
}
/**
* Fallback: apply via charID-based descriptor.
*/
function applyInnerShadowCharID(params) {
try {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc.putReference(charIDToTypeID("null"), ref);
var fxDesc = new ActionDescriptor();
var isDesc = new ActionDescriptor();
isDesc.putBoolean(charIDToTypeID("enab"), true);
isDesc.putBoolean(charIDToTypeID("present"), true);
isDesc.putBoolean(charIDToTypeID("showInDialog"), true);
// Blend mode
isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), charIDToTypeID(params.blendModeCharID));
// Color
var clr = new ActionDescriptor();
clr.putDouble(charIDToTypeID("Rd "), 0);
clr.putDouble(charIDToTypeID("Grn "), 0);
clr.putDouble(charIDToTypeID("Bl "), 0);
isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);
// Opacity: 0-100 integer
isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);
// Angle: use lagl (local angle) instead of Angl
isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
isDesc.putBoolean(charIDToTypeID("uglg"), false);
// Distance
isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);
// Choke
isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);
// Blur
isDesc.putInteger(charIDToTypeID("blur"), params.size);
// Noise
isDesc.putInteger(charIDToTypeID("Nose"), 0);
fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);
desc.putObject(charIDToTypeID("T "), charIDToTypeID("Lefx"), fxDesc);
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
return true;
} catch (e) {
$.writeln("charID failed: " + e);
return false;
}
}
function saveTransparentPng(doc, file) {
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 6;
pngOptions.interlaced = false;
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
}
var count = 0;
var errors = 0;
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;
var blendModeCharMap = {
"normal": "Nrml",
"multiply": "Mltp"
};
for (var a = 0; a < angles.length; a++) {
for (var d = 0; d < distances.length; d++) {
for (var s = 0; s < sizes.length; s++) {
for (var c = 0; c < chokes.length; c++) {
for (var o = 0; o < fxOpacities.length; o++) {
for (var b = 0; b < fxBlendModes.length; b++) {
var bmName = fxBlendModes[b].name;
var bmID = fxBlendModes[b].id;
var fileName = "is_" +
"a" + angles[a] + "_" +
"d" + distances[d] + "_" +
"s" + sizes[s] + "_" +
"c" + chokes[c] + "_" +
"op" + fxOpacities[o] + "_" +
"bm" + bmName +
".png";
var file = new File(outFolder + "/" + fileName);
if (file.exists) {
count++;
continue;
}
var params = {
angle: angles[a],
distance: distances[d],
size: sizes[s],
choke: chokes[c],
opacity: fxOpacities[o],
blendMode: bmID,
blendModeCharID: blendModeCharMap[bmID] || "Nrml"
};
var doc = createDocument("inner_shadow_test", 256, 256);
var layer = addBlackRectangle(doc);
var applied = applyInnerShadow(params);
if (!applied) {
applied = applyInnerShadowCharID(params);
}
if (applied) {
try {
saveTransparentPng(doc, file);
} catch (e2) {
$.writeln("Save error: " + fileName + " — " + e2);
errors++;
}
} else {
$.writeln("FAILED to apply effect: " + fileName);
errors++;
}
doc.close(SaveOptions.DONOTSAVECHANGES);
count++;
if (count % 10 === 0) {
$.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
}
}
}
}
}
}
}
alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""Test Photopea inner shadow via CDP."""
import json, time, subprocess, sys, os
CHROMIUM = "/snap/bin/chromium"
PORT = 9555
OUT_DIR = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/photopea_test"
os.makedirs(OUT_DIR, exist_ok=True)
def http_get(path):
import urllib.request
with urllib.request.urlopen(f"http://127.0.0.1:{PORT}{path}", timeout=10) as r:
return json.loads(r.read())
class CDP:
def __init__(self, ws_url):
import websocket
self.ws = websocket.create_connection(ws_url, timeout=60)
self._id = 0
def send(self, method, params=None):
self._id += 1
msg = {"id": self._id, "method": method}
if params:
msg["params"] = params
self.ws.send(json.dumps(msg))
while True:
resp = json.loads(self.ws.recv())
if resp.get("id") == self._id:
return resp
def eval_js(self, expr, await_promise=True, timeout_ms=45000):
r = self.send("Runtime.evaluate", {
"expression": expr,
"awaitPromise": await_promise,
"returnByValue": True,
"timeout": timeout_ms,
})
val = r.get("result", {}).get("result", {})
if "value" in val:
return val["value"]
return val
def close(self):
try: self.ws.close()
except: pass
def main():
proc = subprocess.Popen([
CHROMIUM,
"--headless=new",
"--no-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
f"--remote-debugging-port={PORT}",
"--remote-allow-origins=*",
"--window-size=1024,768",
"about:blank",
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(4)
try:
targets = http_get("/json")
ws_url = None
for t in targets:
if t.get("type") == "page":
ws_url = t["webSocketDebuggerUrl"]
break
if not ws_url:
print("FATAL: no page target")
return 1
cdp = CDP(ws_url)
print(f"Connected ({ws_url[:60]}...)")
print("[1] Navigating to photopea.com...")
cdp.send("Page.navigate", {"url": "https://www.photopea.com"})
print("[2] Waiting for page load...")
time.sleep(90)
r = cdp.eval_js('document.readyState', await_promise=False)
print(f" readyState={r}")
# Check what globals exist
print("[3] Probing global scope...")
probe = cdp.eval_js("""
JSON.stringify({
hasApp: typeof app !== "undefined",
hasApp2: typeof window.app !== "undefined",
hasPhotoshop: typeof Photoshop !== "undefined",
hasPhotoshopApp: typeof photoshop !== "undefined",
bodyLen: document.body ? document.body.innerHTML.length : 0,
scripts: document.scripts.length,
title: document.title,
iframes: document.querySelectorAll("iframe").length,
})
""", await_promise=False)
print(f" globals: {probe}")
# Photopea uses a special mechanism - try postMessage approach
# Photopea might listen for messages with specific format
print("[4] Trying Photopea postMessage API...")
# Photopea's API: when loaded in an iframe, parent sends scripts via postMessage.
# But we're ON the photopea.com page. Let's try app.executeScript or direct eval.
r2 = cdp.eval_js("""
(function() {
// Try to find app in various scopes
var found = [];
try { if (window.app) found.push("window.app"); } catch(e) {}
try { if (self.app) found.push("self.app"); } catch(e) {}
try { if (globalThis.app) found.push("globalThis.app"); } catch(e) {}
// Check if there are any Photopea-specific globals
var keys = Object.keys(window).filter(k =>
k.toLowerCase().includes("photo") ||
k.toLowerCase().includes("ppea") ||
k.toLowerCase().includes("pp") ||
k.toLowerCase().includes("app")
);
return JSON.stringify({found: found, photoKeys: keys.slice(0, 20)});
})()
""", await_promise=False)
print(f" probe result: {r2}")
# Try running a script via Photopea's mechanism
# Photopea can receive scripts via URL hash: photopea.com/#script_here
# But more importantly, it exposes app when loaded as intended
print("[5] Trying hash-based script execution...")
script = 'var doc = app.documents.add(256, 256, 72, "test", NewDocumentMode.RGB, DocumentFill.TRANSPARENT); app.echoToOE("OK");'
import base64
b64 = base64.b64encode(script.encode()).decode()
cdp.send("Page.navigate", {"url": f"https://www.photopea.com#{script}"})
time.sleep(15)
r3 = cdp.eval_js('typeof app !== "undefined" ? "app_FOUND" : "no_app"', await_promise=False)
print(f" after hash nav: {r3}")
# Maybe we need to add --enable-webgl or similar
# Let's try a completely different approach: use Photopea's RPC mechanism
print("[6] Checking if Photopea uses MessagePort or ServiceWorker...")
r4 = cdp.eval_js("""
(function() {
var info = {};
try { info.workers = navigator.serviceWorker ? "available" : "not available"; } catch(e) { info.workers = "err:" + e; }
try { info.sockets = typeof WebSocket !== "undefined" ? "available" : "not"; } catch(e) {}
// Check for MessageChannel
try {
var ch = new MessageChannel();
info.messageChannel = "available";
} catch(e) { info.messageChannel = "err"; }
// Check canvas elements (Photopea renders to canvas)
var canvases = document.querySelectorAll("canvas");
info.canvasCount = canvases.length;
info.canvasDims = [];
canvases.forEach(function(c) { info.canvasDims.push(c.width + "x" + c.height); });
return JSON.stringify(info);
})()
""", await_promise=False)
print(f" {r4}")
# Last resort: check the actual Photopea API docs
# Photopea exposes `app` on window when the UI is loaded
# It might need WebGL canvas to be created first
# Let's check if there's a #pea-app or similar container
r5 = cdp.eval_js("""
(function() {
var divs = document.querySelectorAll("div[id]");
var ids = [];
divs.forEach(function(d) { ids.push(d.id); });
return JSON.stringify(ids.slice(0, 30));
})()
""", await_promise=False)
print(f" div ids: {r5}")
print("\n=== CONCLUSION ===")
print("Photopea loaded but `app` is NOT in the global JS scope.")
print("This means Photopea's scripting API is only available via postMessage")
print("when Photopea is embedded as an iframe in another page.")
print("")
print("The correct approach for batch generation is:")
print(" - The user runs the JSX script manually in Photopea's Script runner")
print(" - OR use a different tool (GIMP Script-Fu, Python PIL)")
return 0
except Exception as e:
print(f"EXCEPTION: {e}")
import traceback
traceback.print_exc()
return 1
finally:
try: cdp.close()
except: pass
try: proc.kill()
except: pass
try: proc.wait(timeout=5)
except: pass
if __name__ == "__main__":
sys.exit(main() or 0)
+41
View File
@@ -0,0 +1,41 @@
# Photoshop ExtendScript Dataset Generator
This folder contains JSX scripts to generate systematic layer-effect ground-truth images in Adobe Photoshop.
## Files
- `generate_inner_shadow_dataset.jsx` — Starter script for Inner Shadow.
## Usage
1. Open Adobe Photoshop.
2. Choose `File > Scripts > Browse...`.
3. Select `generate_inner_shadow_dataset.jsx`.
4. Choose an output folder when prompted.
Photoshop will create a new document for each parameter combination, draw a filled black rectangle, apply the Inner Shadow effect, and export a transparent PNG.
## Current parameter grid (short version)
| Parameter | Values |
|---|---|
| angle | 0, 90, 180, 270 |
| distance | 5, 15, 30 |
| size | 10, 30 |
| choke | 0, 20 |
| fx opacity | 75, 100 |
| fx blend mode | Normal, Multiply |
Total images: `4 × 3 × 2 × 2 × 2 × 2 = 192`.
## Notes
- Blend mode keys use Photoshop's 4-character codes (`Nrml`, `Mltp`).
- The output PNGs have transparent backgrounds, so MAE comparisons can ignore white reference pixels if needed.
- Opacity is passed as 0-100 (e.g. 75 means 75%).
- If a combination fails, the script alerts and continues with the next one.
## Next steps
- Add layer blend mode, layer opacity, and fill opacity variations.
- Extend to Drop Shadow, Outer Glow, Inner Glow, Stroke, Bevel & Emboss, Color Overlay.
+25
View File
@@ -0,0 +1,25 @@
# Photoshop Batch PNG Exporter
This script batch-exports PSD files to transparent PNGs.
## Files
- `export_psd_folder_to_png.jsx`
## Usage
1. Open Adobe Photoshop.
2. Choose `File > Scripts > Browse...`.
3. Select `export_psd_folder_to_png.jsx`.
4. When prompted, choose the folder containing the PSD files generated by `generate_inner_shadow_psd_set.rs`.
5. The script saves one PNG next to each PSD.
## Notes
- Existing PNGs are skipped, so you can re-run safely.
- If any PSD fails to open or export, the script logs it and continues.
- Output PNGs use transparent background.
## Photopea alternative
Photopea does not support transparent PNG export via JSX directly. For Photopea, open each PSD and use `File > Export As > PNG` with transparency enabled.
@@ -0,0 +1,57 @@
/**
* export_psd_folder_to_png.jsx
*
* Purpose:
* Batch-open every PSD in a folder and export it as a transparent PNG.
*
* Logic & Workflow:
* 1. Prompts the user to select a folder containing PSD files.
* 2. Iterates over all *.psd files in that folder.
* 3. Opens each PSD, saves it as PNG with transparency, then closes without saving.
*
* Arguments:
* None (interactive). User selects input folder via dialog.
*
* Side Effects:
* - Opens and closes many Photoshop documents.
* - Writes PNG files next to the original PSDs.
*/
var inFolder = Folder.selectDialog("Select folder containing PSD files");
if (!inFolder) {
alert("No folder selected. Exiting.");
throw new Error("No folder");
}
var psdFiles = inFolder.getFiles("*.psd");
if (psdFiles.length === 0) {
alert("No PSD files found in " + inFolder.fsName);
throw new Error("No PSD files");
}
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 6;
pngOptions.interlaced = false;
for (var i = 0; i < psdFiles.length; i++) {
var psdFile = psdFiles[i];
var baseName = psdFile.name.replace(/\.psd$/i, "");
var pngFile = new File(inFolder + "/" + baseName + ".png");
if (pngFile.exists) {
$.writeln("Skipping existing: " + pngFile.name);
continue;
}
try {
var doc = app.open(psdFile);
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
doc.close(SaveOptions.DONOTSAVECHANGES);
$.writeln("Exported: " + pngFile.name);
} catch (e) {
$.writeln("Error processing " + psdFile.name + ": " + e);
alert("Error processing " + psdFile.name + ":\n" + e);
}
}
alert("Done. Processed " + psdFiles.length + " PSD files.");
@@ -0,0 +1,73 @@
/**
* export_psd_folder_to_png.jsx
*
* Purpose:
* Batch-open every PSD in a folder and export it as a transparent PNG
* into a separate output folder.
*
* Logic & Workflow:
* 1. Prompts user for input PSD folder.
* 2. Prompts user for output PNG folder (default: input + "_png" suffix).
* 3. Iterates over all *.psd files.
* 4. Opens each PSD, saves as PNG with transparency, closes without saving.
*
* Arguments:
* None (interactive).
*
* Side Effects:
* - Opens and closes many Photoshop documents.
* - Writes PNG files into the output folder.
*/
var inFolder = Folder.selectDialog("Select PSD input folder");
if (!inFolder) {
alert("No folder selected. Exiting.");
throw new Error("No folder");
}
var defaultOut = new Folder(inFolder.parent.fsName + "/" + inFolder.name + "_png");
var outFolder = Folder.selectDialog("Select PNG output folder", defaultOut);
if (!outFolder) {
outFolder = defaultOut;
}
if (!outFolder.exists) {
outFolder.create();
}
var psdFiles = inFolder.getFiles("*.psd");
if (psdFiles.length === 0) {
alert("No PSD files found in " + inFolder.fsName);
throw new Error("No PSD files");
}
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 6;
pngOptions.interlaced = false;
for (var i = 0; i < psdFiles.length; i++) {
var psdFile = psdFiles[i];
var baseName = psdFile.name.replace(/\.psd$/i, "");
var pngFile = new File(outFolder.fsName + "/" + baseName + ".png");
if (pngFile.exists) {
$.writeln("Skipping existing: " + pngFile.name);
continue;
}
try {
var doc = app.open(psdFile);
// Ensure no background layer blocks transparency
if (doc.backgroundLayer) {
doc.activeLayer = doc.backgroundLayer;
doc.activeLayer.remove();
}
doc.trim(TrimType.TRANSPARENT);
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
doc.close(SaveOptions.DONOTSAVECHANGES);
$.writeln("Exported: " + pngFile.name);
} catch (e) {
$.writeln("Error processing " + psdFile.name + ": " + e);
}
}
alert("Done. Processed " + psdFiles.length + " PSD files.");
@@ -0,0 +1,185 @@
/**
* generate_inner_shadow_dataset.jsx
*
* Purpose:
* Generate a systematic Inner Shadow effect dataset in Adobe Photoshop.
*
* Logic & Workflow:
* 1. Creates a new transparent RGB document.
* 2. Adds a raster layer and fills a centered rectangle with black.
* 3. Applies one Inner Shadow effect via Photoshop ActionDescriptor.
* 4. Saves the result as a transparent PNG.
*
* Arguments:
* None (interactive). The user selects an output folder via dialog.
*
* Side Effects:
* - Creates and closes many temporary Photoshop documents.
* - Writes PNG files to the selected folder.
*/
var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
if (!outFolder) {
alert("No output folder selected. Exiting.");
throw new Error("No output folder");
}
var angles = [0, 90, 180, 270];
var distances = [5, 15, 30];
var sizes = [10, 30];
var chokes = [0, 20];
var fxOpacities = [75, 100];
var fxBlendModes = [
{ key: "Nrml", name: "normal" },
{ key: "Mltp", name: "multiply" }
];
function pxValue(v) {
if (v === undefined || v === null) return 0;
if (typeof v === "number") return v;
if (typeof v.as === "function") return v.as("px");
if (typeof v.value !== "undefined") return v.value;
return Number(v);
}
function createDocument(name, width, height) {
try {
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
} catch (e) {
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB);
}
}
function addBlackRectangle(doc) {
var w = pxValue(doc.width);
var h = pxValue(doc.height);
var margin = Math.round(Math.min(w, h) * 0.25);
var layer = doc.artLayers.add();
layer.name = "shape";
var region = [
[margin, margin],
[w - margin, margin],
[w - margin, h - margin],
[margin, h - margin]
];
doc.selection.select(region);
var black = new SolidColor();
black.rgb.red = 0;
black.rgb.green = 0;
black.rgb.blue = 0;
doc.selection.fill(black);
doc.selection.deselect();
return layer;
}
function blendModeTypeID(mode) {
try { return charIDToTypeID(mode); }
catch (e) { return stringIDToTypeID(mode); }
}
function applyInnerShadow(params) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc.putReference(charIDToTypeID("null"), ref);
var fxDesc = new ActionDescriptor();
var isDesc = new ActionDescriptor();
isDesc.putBoolean(charIDToTypeID("enab"), true);
isDesc.putBoolean(charIDToTypeID("present"), true);
isDesc.putBoolean(charIDToTypeID("showInDialog"), true);
isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), blendModeTypeID(params.blendMode));
var clr = new ActionDescriptor();
clr.putDouble(charIDToTypeID("Rd "), 0);
clr.putDouble(charIDToTypeID("Grn "), 0);
clr.putDouble(charIDToTypeID("Bl "), 0);
isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);
isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);
isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
isDesc.putBoolean(charIDToTypeID("uglg"), false);
isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);
isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);
isDesc.putInteger(charIDToTypeID("blur"), params.size);
isDesc.putInteger(charIDToTypeID("Nose"), 0);
fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);
desc.putObject(charIDToTypeID("T "), charIDToTypeID("Lefx"), fxDesc);
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
}
function saveTransparentPng(doc, file) {
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 6;
pngOptions.interlaced = false;
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
}
var count = 0;
var errors = 0;
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;
for (var a = 0; a < angles.length; a++) {
for (var d = 0; d < distances.length; d++) {
for (var s = 0; s < sizes.length; s++) {
for (var c = 0; c < chokes.length; c++) {
for (var o = 0; o < fxOpacities.length; o++) {
for (var b = 0; b < fxBlendModes.length; b++) {
var params = {
angle: angles[a],
distance: distances[d],
size: sizes[s],
choke: chokes[c],
opacity: fxOpacities[o],
blendMode: fxBlendModes[b].key
};
var fileName = "is_" +
"a" + params.angle + "_" +
"d" + params.distance + "_" +
"s" + params.size + "_" +
"c" + params.choke + "_" +
"op" + params.opacity + "_" +
"bm" + fxBlendModes[b].name +
".png";
var file = new File(outFolder + "/" + fileName);
if (file.exists) { count++; continue; }
var doc = createDocument("inner_shadow_test", 256, 256);
var layer = addBlackRectangle(doc);
try {
applyInnerShadow(params);
saveTransparentPng(doc, file);
} catch (e) {
$.writeln("Error: " + fileName + " — " + e);
errors++;
} finally {
doc.close(SaveOptions.DONOTSAVECHANGES);
}
count++;
if (count % 10 === 0) {
$.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
}
}
}
}
}
}
}
alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Minimal PSD writer — byte-exact per Adobe Photoshop File Formats Specification.
Creates a 256x256 RGBA document with one layer (red rectangle).
"""
import struct, os
def be16(v): return struct.pack(">H", v)
def be32(v): return struct.pack(">I", v)
def bei16(v): return struct.pack(">h", v)
def packbits_compress(row):
out = bytearray()
n = len(row)
i = 0
while i < n:
# find run
run = 1
while run < 128 and i + run < n and row[i+run] == row[i]:
run += 1
if run > 1:
out.append((1 - run) & 0xFF)
out.append(row[i])
i += run
else:
# literal
start = i
lit = 0
while lit < 128 and i + lit < n:
nxt = 0
if i + lit + 1 < n and row[i+lit] == row[i+lit+1]:
r = 2
while r < 128 and i+lit+r < n and row[i+lit+r] == row[i+lit]:
r += 1
nxt = r
if nxt >= 3:
break
lit += 1
if lit == 0:
lit = 1
out.append(lit - 1)
out.extend(row[start:start+lit])
i += lit
return bytes(out)
def write_resource(buf, res_id, name, data):
buf.extend(b"8BIM")
buf.extend(be16(res_id))
name_padded = name + b'\x00' if (1 + len(name)) % 2 == 1 else name
buf.append(len(name))
buf.extend(name)
if (1 + len(name)) % 2 == 1:
buf.append(0)
buf.extend(be32(len(data)))
buf.extend(data)
if len(data) % 2 != 1:
pass # even already
# pad data to even
if len(data) % 2 != 0:
buf.append(0)
def write_pascal_padded4(buf, s):
"""Write Pascal string padded to multiple of 4 bytes."""
b = s.encode("ascii") if isinstance(s, str) else s
buf.append(len(b))
buf.extend(b)
total = 1 + len(b)
pad = (4 - total % 4) % 4
buf.extend(b'\x00' * pad)
def write_tagged_block(buf, key, data):
"""Write 8BIM tagged block."""
buf.extend(b"8BIM")
buf.extend(key)
buf.extend(be32(len(data)))
buf.extend(data)
if len(data) % 2 != 0:
buf.append(0)
def make_descriptor_string(s):
"""Write a descriptor UTF-16 string: length(4) + chars(2*N)."""
enc = s.encode("utf-16-be")
return be32(len(enc) // 2) + enc
def main():
W, H = 256, 256
MARGIN = 64
# ── Create layer pixels ──
rgba = bytearray(W * H * 4)
for y in range(H):
for x in range(W):
i = (y * W + x) * 4
if MARGIN <= x < W - MARGIN and MARGIN <= y < H - MARGIN:
rgba[i] = 255 # R
rgba[i+1] = 0 # G
rgba[i+2] = 0 # B
rgba[i+3] = 255 # A
# ── Header (26 bytes) ──
header = bytearray()
header.extend(b"8BPS")
header.extend(be16(1)) # version
header.extend(b'\x00' * 6) # reserved
header.extend(be16(4)) # channels (RGBA)
header.extend(be32(H)) # height
header.extend(be32(W)) # width
header.extend(be16(8)) # depth
header.extend(be16(3)) # color mode (RGB)
# ── Color Mode Data ──
color_mode = be32(0)
# ── Image Resources ──
img_res = bytearray()
# 0x03ED: Resolution info
res_data = bytearray()
res_data.extend(be32(72 << 16)) # hRes (72 DPI fixed)
res_data.extend(be16(1)) # hResUnit (1=pixels/inch)
res_data.extend(be16(1)) # vResUnit
res_data.extend(be32(72 << 16)) # vRes
res_data.extend(be16(1)) # widthUnit
res_data.extend(be16(1)) # heightUnit
write_resource(img_res, 0x03ED, b"", bytes(res_data))
# 0x0419: Global Angle
write_resource(img_res, 0x0419, b"", be32(120))
# pad img_res to even
if len(img_res) % 2 != 0:
img_res.append(0)
# ── Layer & Mask Information ──
# Build layer info section first
layer_info = bytearray()
# Layer count (positive = no groups)
layer_info.extend(bei16(1))
# Layer record
layer_rec = bytearray()
# Rect: top, left, bottom, right
layer_rec.extend(be32(0)) # top
layer_rec.extend(be32(0)) # left
layer_rec.extend(be32(H)) # bottom
layer_rec.extend(be32(W)) # right
# Channel count
layer_rec.extend(be16(4))
# Channel info: (-1=alpha, 0=R, 1=G, 2=B)
# We'll compute channel data lengths later
ch_ids = [-1, 0, 1, 2]
ch_data_list = []
for ch_id in ch_ids:
plane = bytearray()
for y in range(H):
for x in range(W):
idx = (y * W + x) * 4
if ch_id == -1:
plane.append(rgba[idx + 3])
elif ch_id == 0:
plane.append(rgba[idx])
elif ch_id == 1:
plane.append(rgba[idx + 1])
elif ch_id == 2:
plane.append(rgba[idx + 2])
ch_data_list.append(plane)
# Compress each channel
ch_compressed = []
for plane in ch_data_list:
row_lengths = []
compressed_rows = []
for y in range(H):
row = plane[y * W : (y + 1) * W]
compressed = packbits_compress(row)
row_lengths.append(len(compressed))
compressed_rows.append(compressed)
ch_compressed.append((row_lengths, compressed_rows))
# Channel data = compression(2) + row_lengths(H*2) + compressed_data
ch_packed = []
for row_lengths, compressed_rows in ch_compressed:
buf = bytearray()
buf.extend(be16(1)) # compression type = RLE
for rl in row_lengths:
buf.extend(be16(rl))
for cr in compressed_rows:
buf.extend(cr)
ch_packed.append(bytes(buf))
# Write channel info records
for i, ch_id in enumerate(ch_ids):
layer_rec.extend(bei16(ch_id))
layer_rec.extend(be32(len(ch_packed[i])))
# Blend mode
layer_rec.extend(b"8BIMnorm")
layer_rec.append(255) # opacity
layer_rec.append(0) # clipping (base)
layer_rec.append(0) # flags (visible)
layer_rec.append(0) # filler
# Extra data
extra = bytearray()
# Layer mask data (4 bytes, length=0)
extra.extend(be32(0))
# Blending ranges (4 bytes length=40, then 40 bytes of data)
extra.extend(be32(40))
for _ in range(10):
extra.extend(be16(0))
extra.extend(be16(65535))
# Layer name (Pascal string padded to 4)
write_pascal_padded4(extra, "Red Rectangle")
layer_rec.extend(be32(len(extra)))
layer_rec.extend(extra)
# Append channel data
layer_info.extend(layer_rec)
for ch_data in ch_packed:
layer_info.extend(ch_data)
# Pad layer info to even
if len(layer_info) % 2 != 0:
layer_info.append(0)
# Global Layer Mask Info (empty, 4 bytes)
glmi = be32(0)
# Layer & Mask Information section
lami = bytearray()
lami.extend(be32(len(layer_info) + len(glmi))) # length of rest
lami.extend(layer_info)
lami.extend(glmi)
# ── Merged Image Data ──
merged = bytearray()
merged.extend(be16(1)) # compression = RLE
# Row lengths for all channels (per-channel order: RRR...GGG...BBB...AAA...)
for ch_offset in range(4): # R, G, B, A
for y in range(H):
row = bytes(rgba[(y * W + ch_offset)::4][:W]) # wrong, need proper extraction
row = bytearray()
for x in range(W):
idx = (y * W + x) * 4 + ch_offset
row.append(rgba[idx])
compressed = packbits_compress(row)
merged.extend(be16(len(compressed)))
# Compressed data for all channels
for ch_offset in range(4):
for y in range(H):
row = bytearray()
for x in range(W):
idx = (y * W + x) * 4 + ch_offset
row.append(rgba[idx])
compressed = packbits_compress(row)
merged.extend(compressed)
# ── Assemble PSD ──
psd = bytearray()
psd.extend(header)
psd.extend(color_mode)
psd.extend(be32(len(img_res)))
psd.extend(img_res)
psd.extend(lami)
psd.extend(merged)
out_path = "_tmp/python_ref_test.psd"
with open(out_path, "wb") as f:
f.write(psd)
print(f"Wrote {len(psd)} bytes to {out_path}")
# Also verify roundtrip
assert psd[:4] == b"8BPS"
assert len(psd) == 26 + 4 + 4 + len(img_res) + len(lami) + len(merged)
print("Roundtrip check: OK")
if __name__ == "__main__":
main()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE animation-metadata PUBLIC '-//KDE//DTD krita 1.1//EN' 'http://www.calligra.org/DTD/krita-1.1.dtd'>
<animation-metadata xmlns="http://www.calligra.org/DTD/krita">
<framerate type="value" value="24"/>
<range to="100" type="timerange" from="0"/>
<currentTime type="value" value="0"/>
<export-settings>
<sequenceFilePath type="value" value=""/>
<sequenceBaseName type="value" value=""/>
<sequenceInitialFrameNumber type="value" value="-1"/>
</export-settings>
</animation-metadata>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
VERSION 2
TILEWIDTH 64
TILEHEIGHT 64
PIXELSIZE 4
DATA 0
Binary file not shown.
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document-info PUBLIC '-//KDE//DTD document-info 1.1//EN' 'http://www.calligra.org/DTD/document-info-1.1.dtd'>
<document-info xmlns="http://www.calligra.org/DTD/document-info">
<about>
<title></title>
<description></description>
<subject></subject>
<abstract><![CDATA[]]></abstract>
<keyword></keyword>
<initial-creator>Unknown</initial-creator>
<editing-cycles>1</editing-cycles>
<editing-time>0</editing-time>
<date>2026-06-17T17:50:18</date>
<creation-date>2026-06-17T17:48:05</creation-date>
<language></language>
<license></license>
</about>
<author>
<full-name></full-name>
<creator-first-name></creator-first-name>
<creator-last-name></creator-last-name>
<initial></initial>
<author-title></author-title>
<position></position>
<company></company>
</author>
</document-info>
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE DOC PUBLIC '-//KDE//DTD krita 2.0//EN' 'http://www.calligra.org/DTD/krita-2.0.dtd'>
<DOC xmlns="http://www.calligra.org/DTD/krita" editor="Krita" kritaVersion="5.2.11-prealpha" syntaxVersion="2.0">
<IMAGE height="800" description="" name="Unnamed" mime="application/x-kra" profile="sRGB-elle-V2-srgbtrc.icc" width="800" x-res="72.0000000000058" y-res="72.0000000000058" colorspacename="RGBA">
<layers>
<layer intimeline="0" name="Layer 1" locked="0" collapsed="0" channelflags="" uuid="{e58100bc-fcda-43b3-ac7d-b834b7baacb6}" colorspacename="RGBA" filename="layer2" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{d7a0844e-a522-4673-8c83-aa4f45e7782f}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
<layer intimeline="0" name="Layer 4" locked="0" collapsed="0" channelflags="" uuid="{633fd10a-20f2-4a99-8e29-aa27df65df0e}" colorspacename="RGBA" filename="layer3" opacity="255" compositeop="normal" colorlabel="0" selected="true" layerstyle="{7d378444-e311-41d3-b9c6-a19e2a8e5899}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
<layer intimeline="0" name="Layer 3" locked="0" collapsed="0" channelflags="" uuid="{82aae6cb-88d2-43dd-b32b-8f697a58001f}" colorspacename="RGBA" filename="layer4" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{117f8514-88e4-4553-bccb-44a87b6b9182}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
<layer intimeline="0" name="Layer 2" locked="0" collapsed="0" channelflags="" uuid="{12fdb980-bba4-4d3f-94f0-9a88a529dc76}" colorspacename="RGBA" filename="layer5" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{4bbe8f2c-7ad2-4e31-8af0-9ad6715cacf4}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
<layer filename="layer6" y="0" intimeline="0" name="Background" visible="1" channellockflags="" opacity="255" nodetype="paintlayer" channelflags="" uuid="{c9e22005-6a14-42ff-bfc6-32ce0b70f20c}" x="0" onionskin="0" locked="0" compositeop="normal" collapsed="0" colorlabel="0" colorspacename="RGBA"/>
</layers>
<ProjectionBackgroundColor ColorData="AAAAAA=="/>
<GlobalAssistantsColor SimpleColorData="176,176,176,255"/>
<Palettes/>
<resources/>
<animation>
<framerate type="value" value="24"/>
<range to="100" type="timerange" from="0"/>
<currentTime type="value" value="0"/>
</animation>
<Annotations>
<Annotation description="0x0408 - Grid &amp; guide info" type="PSD Resource Block: 1032"/>
<Annotation description="0x040c - Thumbnail resource" type="PSD Resource Block: 1036"/>
<Annotation description="0x040d - Global angle" type="PSD Resource Block: 1037"/>
<Annotation description="0x0414 - Document specific IDs" type="PSD Resource Block: 1044"/>
<Annotation description="0x0415 - Unicode alpha names" type="PSD Resource Block: 1045"/>
<Annotation description="0x0419 - Global altitude" type="PSD Resource Block: 1049"/>
<Annotation description="0x041a - Slices" type="PSD Resource Block: 1050"/>
<Annotation description="0x0435 - (Photoshop CS3) DisplayInfo structure to support floating point colors. Also see ID 1007. See Appendix A in Photoshop API Guide.pdf ." type="PSD Resource Block: 1077"/>
</Annotations>
</IMAGE>
</DOC>
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+1
View File
@@ -0,0 +1 @@
application/x-krita
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB