Skip to content

Commit

Permalink
Add Python script to slice into numbered variants (I-am-Erk#1618)
Browse files Browse the repository at this point in the history
* add a script to slice into numbered variants, minor fixes

* add slice_variants.py to the docs
  • Loading branch information
mlange-42 authored Sep 26, 2022
1 parent 015363e commit d26d239
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 7 deletions.
14 changes: 14 additions & 0 deletions doc/how-to/autotiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,17 @@ when you want to adjust all sprites as one image:
cd mud_tiles
$ tools/unslice_multitile.py mud
```

### Slicing variants

Randomly selected sprite variants can be used based on weights.
For easier creation of these variants, multitile-like images can be sliced into numbered variant sprites using `slice_variants.py`.

Usage is the same as with `slice_multitile.py`. E.g.:

```sh
$ tools/slice_variants.py t_floor_multitile.png 32 32
```

The script can handle any multitile size, not only 4x4.
For iso multitiles, use switch `--iso`.
18 changes: 13 additions & 5 deletions tools/slice_multitile.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ def main(args):
if rearrange is not None:
template_size = 5 if len(slices) == 25 else 4
order = OUTPUT_ORDER[template_size]

img_out = pyvips.Image.new_from_array(
np.full(shape=(rearrange * template_size, args.width * template_size, 4),
fill_value=1, dtype=np.uint8),
interpretation="rgb")

for col in range(template_size):
for row in range(template_size):
sprite = slices[slicing_map[order[col + template_size * row]]]
Expand All @@ -211,7 +211,7 @@ def main(args):

if args.no_json:
return

if len(slices) != 25:
json_content = { # double quotes here to make copying easier
"id": args.tile,
Expand Down Expand Up @@ -326,6 +326,14 @@ def iso_mask(width, height):


def extract_slices(img, width, height, iso):
try:
if not img.hasalpha():
img = img.addalpha()
if img.get_typeof('icc-profile-data') != 0:
img = img.icc_transform('srgb')
except Vips.Error as vips_error:
raise Exception(vips_error)

slices = []

if iso:
Expand All @@ -335,7 +343,7 @@ def extract_slices(img, width, height, iso):
if width != 2 * height:
raise Exception(
'Only tiles with a width:height ration 2:1 are supported in ISO mode.')

template_size = 0
if img.width == 4 * width and img.height == 4 * height:
template_size = 4
Expand All @@ -359,7 +367,7 @@ def extract_slices(img, width, height, iso):
for col in range(per_row):
s = img.crop(x_offset + col * width, y_offset, width, height)
masked = s.numpy() * mask
slices.append(pyvips.Image.new_from_array(masked, interpretation="rgb"))
slices.append(pyvips.Image.new_from_array(masked, interpretation="srgb"))

else:
for y in range(0, img.height, height):
Expand Down
125 changes: 125 additions & 0 deletions tools/slice_variants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
'''
Slice multitiles of arbitrary size into numbered variants.
'''

import argparse
import os
import pathlib

from slice_multitile import iso_mask

try:
vips_path = os.getenv("LIBVIPS_PATH")
if vips_path is not None and vips_path != "":
os.environ["PATH"] += ";"+os.path.join(vips_path, "bin")
import pyvips
Vips = pyvips
except ImportError:
import gi
gi.require_version('Vips', '8.0') # NoQA
from gi.repository import Vips


def main(args):
args.height = args.height or args.width

if args.tile is None:
args.tile = pathlib.Path(args.image).stem \
.replace('autotile_', '').replace('multitile_', '') \
.replace('_autotile', '').replace('_multitile', '') # TODO: regex

output_dir = args.out or os.path.join(
os.path.dirname(args.image), args.tile)

img = pyvips.Image.new_from_file(args.image)

slices = extract_slices(img, args.width, args.height, args.iso)

pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)

for i, sl in enumerate(slices):
out_idx = i
while args.append and os.path.isfile(os.path.join(output_dir, f'{args.tile}_{out_idx:02d}.png')):
out_idx += 1
sl.pngsave(
os.path.join(output_dir, f'{args.tile}_{out_idx:02d}.png'))


def extract_slices(img, width, height, iso):
try:
if not img.hasalpha():
img = img.addalpha()
if img.get_typeof('icc-profile-data') != 0:
img = img.icc_transform('srgb')
except Vips.Error as vips_error:
raise Exception('%s', vips_error)

slices = []

template_size_x = img.width // width
template_size_y = img.height // height
if img.width != template_size_x * width or img.height != template_size_y * height:
raise Exception(
f"Unexpected image size. Expecting integer multiple of tile size."
)

if iso:
if width % 2 != 0 or height % 2 != 0:
raise Exception(
'Only even width and height values are supported in ISO mode.')
if width != 2 * height:
raise Exception(
'Only tiles with a width:height ration 2:1 are supported in ISO mode.')

mask = iso_mask(width, height)

dx = width / 2
dy = height / 2
double_size = 2 * template_size_x
for row in range(double_size - 1):
per_row = min(row, double_size - 2 - row) + 1
half_offsets = (double_size - 2 * per_row) / 2
x_offset = half_offsets * dx
y_offset = row * dy
for col in range(per_row):
s = img.crop(x_offset + col * width, y_offset, width, height)
masked = s.numpy() * mask
slices.append(pyvips.Image.new_from_array(masked, interpretation="srgb"))

else:
for y in range(0, img.height, height):
for x in range(0, img.width, width):
slices.append(img.crop(x, y, width, height))

return slices


if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Slice a multitile image")
parser.add_argument(
"image",
help="path to the multitile image that will be sliced")
parser.add_argument(
"width", type=int,
help="tile width in pixels")
parser.add_argument(
"height", type=int,
nargs='?',
help="tile height in pixels, defaults to tile width")
parser.add_argument(
"--tile", dest="tile",
help="base name of the tile, defaults to the image name"
" without .png, autotile_ and/or multitile_ parts")
parser.add_argument(
"--out", dest="out",
help="output directory path, "
"defaults to the tile name in the directory of the image")
parser.add_argument(
"--iso", action='store_true',
help="slice iso multitile")
parser.add_argument(
"--append", action='store_true',
help="do not overwrite numbered sprites, write with increased IDs")

main(parser.parse_args())
4 changes: 2 additions & 2 deletions tools/unslice_multitile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
'''
Combine a set of multitile sprites into 4x4 grid
Combine a set of multitile sprites into 4x4 or 5x5 grid grid
'''

import argparse
Expand Down Expand Up @@ -29,7 +29,7 @@ def main(args):
height = None
num_sprites = 25 if os.path.isfile(os.path.join(args.path, f'{args.tile}_unconnected_faceN.png')) else 16
template_size = math.isqrt(num_sprites)

id_map = MAPS_ISO[num_sprites] if args.iso else MAPS[num_sprites]
for suffix, position in id_map.items():
sprite = pyvips.Image.new_from_file(os.path.join(args.path, f'{args.tile}_{suffix}.png'))
Expand Down

0 comments on commit d26d239

Please sign in to comment.