154 lines
3.6 KiB
Python
154 lines
3.6 KiB
Python
import argparse
|
|
import os
|
|
import zipfile
|
|
import tempfile
|
|
import shutil
|
|
import numpy as np
|
|
from PIL import Image, ImageChops
|
|
import xml.etree.ElementTree as ET
|
|
|
|
NUM_LAYERS = 4
|
|
MASKS_PER_LAYER = 3
|
|
NOISE_SCALES = [4, 16, 64]
|
|
|
|
|
|
def noise_mask(w, h, scale):
|
|
sw = max(1, w // scale)
|
|
sh = max(1, h // scale)
|
|
|
|
noise = np.random.rand(sh, sw)
|
|
img = Image.fromarray((noise * 255).astype(np.uint8), "L")
|
|
img = img.resize((w, h), Image.BILINEAR)
|
|
|
|
return img
|
|
|
|
|
|
def build_stack_xml(w, h, groups):
|
|
image = ET.Element(
|
|
"image",
|
|
{"version": "0.0.3", "w": str(w), "h": str(h)}
|
|
)
|
|
|
|
root_stack = ET.SubElement(image, "stack")
|
|
|
|
for group in groups:
|
|
|
|
group_el = ET.SubElement(
|
|
root_stack,
|
|
"stack",
|
|
{"name": group["name"]}
|
|
)
|
|
|
|
for layer in group["layers"]:
|
|
layer_attrs = {
|
|
"name": layer["name"],
|
|
"src": layer["src"],
|
|
"opacity": "1.0"
|
|
}
|
|
ET.SubElement(
|
|
group_el,
|
|
"layer",
|
|
layer_attrs
|
|
)
|
|
|
|
return ET.tostring(image, encoding="utf-8", xml_declaration=True)
|
|
|
|
|
|
def build_ora(input_png, output_ora):
|
|
|
|
base = Image.open(input_png).convert("RGBA")
|
|
w, h = base.size
|
|
|
|
tmp = tempfile.mkdtemp()
|
|
|
|
try:
|
|
|
|
data_dir = os.path.join(tmp, "data")
|
|
thumb_dir = os.path.join(tmp, "Thumbnails")
|
|
|
|
os.makedirs(data_dir)
|
|
os.makedirs(thumb_dir)
|
|
|
|
groups = []
|
|
|
|
for i in range(NUM_LAYERS):
|
|
|
|
group_layers = []
|
|
|
|
for j in range(MASKS_PER_LAYER):
|
|
mask = noise_mask(w, h, NOISE_SCALES[j])
|
|
|
|
# Apply mask to alpha channel of base image
|
|
masked_image = base.copy()
|
|
r, g, b, a = masked_image.split()
|
|
# Multiply existing alpha by mask
|
|
new_alpha = ImageChops.multiply(a, mask)
|
|
masked_image.putalpha(new_alpha)
|
|
|
|
layer_path = f"data/layer_{i}_{j}.png"
|
|
masked_image.save(os.path.join(tmp, layer_path))
|
|
|
|
group_layers.append({
|
|
"name": f"layer {j}",
|
|
"src": layer_path
|
|
})
|
|
|
|
groups.append({
|
|
"name": f"group {i}",
|
|
"layers": group_layers
|
|
})
|
|
|
|
stack_xml = build_stack_xml(w, h, groups)
|
|
|
|
with open(os.path.join(tmp, "stack.xml"), "wb") as f:
|
|
f.write(stack_xml)
|
|
|
|
with open(os.path.join(tmp, "mimetype"), "w") as f:
|
|
f.write("image/openraster")
|
|
|
|
base.save(os.path.join(tmp, "mergedimage.png"))
|
|
|
|
thumb = base.copy()
|
|
thumb.thumbnail((256, 256))
|
|
thumb.save(os.path.join(thumb_dir, "thumbnail.png"))
|
|
|
|
with zipfile.ZipFile(output_ora, "w") as z:
|
|
|
|
z.write(
|
|
os.path.join(tmp, "mimetype"),
|
|
"mimetype",
|
|
compress_type=zipfile.ZIP_STORED
|
|
)
|
|
|
|
for root, _, files in os.walk(tmp):
|
|
for file in files:
|
|
|
|
if file == "mimetype":
|
|
continue
|
|
|
|
full = os.path.join(root, file)
|
|
rel = os.path.relpath(full, tmp)
|
|
|
|
z.write(full, rel)
|
|
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate ORA with grouped layers and mask layers"
|
|
)
|
|
|
|
parser.add_argument("input_png")
|
|
parser.add_argument("output_ora")
|
|
|
|
args = parser.parse_args()
|
|
|
|
build_ora(args.input_png, args.output_ora)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|