diff --git a/scripts/img2tex.py b/scripts/img2tex.py index 1eee20c..32e9915 100755 --- a/scripts/img2tex.py +++ b/scripts/img2tex.py @@ -6,38 +6,60 @@ import sys import os.path import argparse -from PIL import Image +from PIL import Image, ImageFilter, ImageOps -def img2tex(filename, varname, invert, resize, outf): +def img2tex(filename, opts, outf): indent = " " * 4 im = Image.open(filename).convert('L') - if resize: - print("Resizing to {}x{}".format(resize[0],resize[1])) - im = im.resize(resize) + if opts.resize: + print("Resizing to {}x{}".format(opts.resize[0], opts.resize[1])) + im = im.resize(opts.resize) + if opts.invert: + print("Inverting luminance.") + im = ImageOps.invert(im) + if opts.blur: + print("Blurring, radius={}.".format(opts.blur)) + im = im.filter(ImageFilter.BoxBlur(opts.blur)) + if opts.rotate: + if opts.rotate in (-90, 270): + print("Rotating 90 degrees clockwise.".format(opts.rotate)) + elif opts.rotate in (90, -270): + print("Rotating 90 degrees counter-clockwise.".format(opts.rotate)) + elif opts.rotate in (180, -180): + print("Rotating 180 degrees.".format(opts.rotate)) + im = im.rotate(opts.rotate, expand=True) + if opts.mirror_x: + print("Mirroring left-to-right.") + im = im.transpose(Image.FLIP_LEFT_RIGHT) + if opts.mirror_y: + print("Mirroring top-to-bottom.") + im = im.transpose(Image.FLIP_TOP_BOTTOM) pix = im.load() width, height = im.size print("// Image {} ({}x{})".format(filename, width, height), file=outf) - - pixmin = 255; - pixmax = 0; - for y in range(height): - for x in range(width): - pixmin = min(pixmin, pix[x,y]) - pixmax = max(pixmax, pix[x,y]) - print("// min = {} max = {}".format(pixmin, pixmax), file=outf) + if opts.range == "dynamic": + pixmin = 255; + pixmax = 0; + for y in range(height): + for x in range(width): + pixmin = min(pixmin, pix[x,y]) + pixmax = max(pixmax, pix[x,y]) + else: + pixmin = 0; + pixmax = 255; + print("// Original luminances: min={}, max={}".format(pixmin, pixmax), file=outf) + print("// Texture heights: min={}, max={}".format(opts.minout, opts.maxout), file=outf) - - print("{} = [".format(varname), file=outf) + print("{} = [".format(opts.varname), file=outf) line = indent for y in range(height): line += "[ " for x in range(width): - if not invert: - line += "{:.2f}, ".format((pix[x,y]-pixmin)/(pixmax-pixmin)) - else: - line += "{:.2f}, ".format((pixmax-pix[x,y])/(pixmax-pixmin)) + u = (pix[x,y] - pixmin) / (pixmax - pixmin) + val = u * (opts.maxout - opts.minout) + opts.minout + line += "{:.3f}".format(val).rstrip('0').rstrip('.') + ", " if len(line) > 60: print(line, file=outf) line = indent * 2 @@ -49,6 +71,13 @@ def img2tex(filename, varname, invert, resize, outf): print("", file=outf) +def check_nonneg_float(value): + val = float(value) + if val < 0: + raise argparse.ArgumentTypeError("{} is an invalid non-negative float value".format(val)) + return val + + def main(): parser = argparse.ArgumentParser(prog='img2tex') parser.add_argument('-o', '--outfile', @@ -56,9 +85,24 @@ def main(): parser.add_argument('-v', '--varname', help='Variable to use in .scad file.') parser.add_argument('-i', '--invert', action='store_true', - help='Invert greyscale values.') + help='Invert luminance values.') parser.add_argument('-r', '--resize', help='Resample image to WIDTHxHEIGHT.') + parser.add_argument('-R', '--rotate', choices=(-270, -180, -90, 0, 90, 180, 270), default=0, type=int, + help='Rotate output by the given number of degrees.') + parser.add_argument('--mirror-x', action="store_true", + help='Mirror output in the X direction.') + parser.add_argument('--mirror-y', action="store_true", + help='Mirror output in the Y direction.') + parser.add_argument('--blur', type=check_nonneg_float, default=0, + help='Perform a box blur on the output with the given radius.') + parser.add_argument('--minout', type=float, default=0.0, + help='The value to output for the minimum luminance.') + parser.add_argument('--maxout', type=float, default=1.0, + help='The value to output for the maximum luminance.') + parser.add_argument('--range', choices=["dynamic", "full"], default="dynamic", + help='If "dynamic", the lowest to brightest luminances are scaled to the minout/maxout range.\n' + 'If "full", 0 to 255 luminances will be scaled to the minout/maxout range.') parser.add_argument('infile', help='Input image file.') opts = parser.parse_args() @@ -71,10 +115,7 @@ def main(): opts.varname = "image_data" size_pat = re.compile(r'^([0-9][0-9]*)x([0-9][0-9]*)$') - if not opts.invert: - opts.invert = False - else: - opts.invert = True + opts.invert = bool(opts.invert) if opts.resize: m = size_pat.match(opts.resize) @@ -89,9 +130,9 @@ def main(): if opts.outfile: with open(opts.outfile, "w") as outf: - img2tex(opts.infile, opts.varname, opts.invert, opts.resize, outf) + img2tex(opts.infile, opts, outf) else: - img2tex(opts.infile, opts.varname, opts.invert, opts.resize, sys.stdout) + img2tex(opts.infile, opts, sys.stdout) sys.exit(0)