-
-
Save methanoliver/2668767d5332aea66a866d2b84692d1c to your computer and use it in GitHub Desktop.
A primitive automatic loader for the simple kind of layered images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
A script to produce (simple) layered images from existing non-layered ones, | |
by designating one image as the base, and then, for every other one, | |
only saving the pixels that are different, and filling the rest with #00000000. | |
You need an installation of Python 3 with Pillow library in it. | |
""" | |
import argparse | |
from PIL import Image | |
parser = argparse.ArgumentParser(description=""" | |
Calculate and save differential images. | |
Resulting files will be named diff#<variant>.webp, which is what the diff-loader expects to see.""") | |
parser.add_argument('source', | |
metavar='SOURCE', | |
type=argparse.FileType('rb'), | |
help="Image that is considered to be the base.") | |
parser.add_argument('files', | |
metavar='FILE', | |
type=argparse.FileType('rb'), | |
nargs='+', | |
help='All other images.') | |
parser.add_argument('--overlay', '-o', | |
action='store_true', | |
help='Assume differentials need to be overlaid on the original first, ' | |
'in case of, e.g. diff images being disembodied heads.') | |
parser.add_argument('--reverse', '-r', | |
action='store_true', | |
help="Do the reverse: go back from differential images to full ones.") | |
args = parser.parse_args() | |
# Output file format args. | |
SAVE_EXT = ".webp" | |
SAVE_FORMAT = { | |
"format": "webp", | |
"lossless": True, | |
"quality": 100, | |
"method": 6 | |
} | |
source_image = Image.open(args.source).convert(mode="RGBA") | |
source_image_px = source_image.load() | |
for diff_file in args.files: | |
diff_image = Image.open(diff_file).convert(mode="RGBA") | |
if diff_image.size != source_image.size: | |
print("Image dimensions for {} do not match, skipping!".format(diff_file.name)) | |
continue | |
if args.reverse: | |
# Reverse mode | |
if not diff_file.name.startswith("diff#"): | |
print("Image {} is not marked with 'diff#' filename tag, skipping.".format(diff_file.name)) | |
continue | |
undiff_image = Image.alpha_composite(source_image, diff_image) | |
fn = diff_file.name.split("diff#", maxsplit=1)[1] | |
fn = fn.rsplit('.',maxsplit=1)[0] + SAVE_EXT | |
undiff_image.save(fn, **SAVE_FORMAT) | |
print(fn, "saved.") | |
else: | |
# Normal mode. | |
if args.overlay: | |
to_image = Image.alpha_composite(source_image, diff_image).load() | |
else: | |
to_image = diff_image.load() | |
difference = Image.new("RGBA", source_image.size, color=(0,0,0,0)) | |
difference_px = difference.load() | |
for y in range(source_image.height): | |
for x in range(source_image.width): | |
r, g, b, a = to_image[x, y] | |
if a != 0 and source_image_px[x, y] != to_image[x, y]: | |
difference_px[x, y] = to_image[x, y] | |
fn = "diff#{}{}".format(diff_file.name.rsplit('.',maxsplit=1)[0], SAVE_EXT) | |
difference.save(fn, **SAVE_FORMAT) | |
print(fn, "saved.") | |
# Also crush and make a base file if it's not a webp. | |
if not args.reverse and not args.source.name.lower().endswith(".webp"): | |
source_image.save("base#"+args.source.name.rsplit('.',1)[0]+SAVE_EXT, **SAVE_FORMAT) | |
print("Done.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Differential image loader loads stacked images generated by | |
# differentiate.py from regular sprites. | |
# | |
# images/sprites/<character>/base#<expression_1>.<ext> | |
# images/sprites/<character>/diff#<expression_2>.<ext> | |
# | |
# diff# will be overlaid on base# and you will get | |
# <character> <expression_1> | |
# <character> <expression_2> | |
# | |
# Things like <character>/<outfit or pose>/<expression> would require | |
# a more complicated naming scheme, so aren't implemented, if you're | |
# up to something that complex, you're better using layeredimage language | |
# as intended. | |
init python hide: | |
# Where we keep the sprites. | |
SPRITE_SOURCE = "images/sprites/" | |
################ | |
import collections | |
# Filter by extension: .png, .jpg, .webp | |
def is_image(fn): | |
return any( | |
fn.lower().endswith(x) | |
for x in ['.png', '.jpg', '.webp'] | |
) | |
def is_hidden(fn): | |
if renpy.os.path.basename(fn).startswith("_"): | |
renpy.log("INFO: File '{}' was hidden".format(fn)) | |
return True | |
return False | |
all_image_files = [x for x in renpy.list_files() if is_image(x) and not is_hidden(x)] | |
# Ignore differential images anywhere except images/sprites. | |
spritefiles = [x for x in all_image_files if x.startswith(SPRITE_SOURCE)] | |
# Count the individual directories, pick out those that have files tagged with "#" | |
# and sort those files into bins per directory. | |
spritedirs = collections.defaultdict(list) | |
for fn in spritefiles: | |
d, f = renpy.os.path.split(fn) | |
if "#" in f: | |
spritedirs[d].append(fn) | |
# And work on each directory: | |
for spritedir, sprites in spritedirs.items(): | |
basefile = None | |
expressions = {} | |
# Make a dict of expressions. | |
# Rules are: | |
# * base#<expression>.<ext> is an expression. | |
# * base#.<ext> is a base file that is never shown alone. | |
# * diff#<expression>.<ext> is an expression overlaid on the nearest base file. | |
# * diff#.<ext> is an error and ignored. | |
# * More than one base# file per directory is a bug. | |
for sprite in sprites: | |
shortfn = renpy.os.path.basename(sprite).rsplit(".", 1)[0] | |
tag, exp = shortfn.split("#",1) | |
if tag == "base": | |
if basefile is not None: | |
renpy.log("ERROR: Differential image directory '{}' contains more than one base file.".format(spritedir)) | |
basefile = sprite | |
if exp: | |
expressions[exp] = sprite | |
elif tag == "diff": | |
if exp: | |
expressions[exp] = sprite | |
else: | |
renpy.log("ERROR: Differential image '{}' has no expression tag.".format(sprite)) | |
else: | |
renpy.log("WARNING: Unrecognized #-tag in '{}'".format(sprite)) | |
if basefile is None: | |
renpy.log("ERROR: Differential image directory '{}' does not appear to contain a base file.".format(spritedir)) | |
continue | |
# Determine the image name. We're assuming no spaces or other separators for now. | |
imagename = spritedir.replace(SPRITE_SOURCE, '') | |
# Build a list of attributes. | |
variants = [] | |
for exp, sprite in expressions.items(): | |
isBase = sprite == basefile | |
variants.append(Attribute( | |
"diff", exp, | |
# Trying to show a diff of absolutely nothing in case the base image | |
# is itself an expression causes an error message. | |
# Trying to plaster a blank alpha layer causes issues with sprite positioning. | |
# So we'll have to just duplicate it and show it twice. | |
image = sprite, | |
default = isBase)) | |
renpy.image(imagename, LayeredImage([basefile] + variants, name=imagename)) | |
renpy.log("Known images: {}".format(renpy.list_images())) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment