170 lines
8.0 KiB
Python
170 lines
8.0 KiB
Python
|
|
#!/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()
|