#!/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'\n\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()