From a635ff68064ab4c8deb88ba6a82fa11c47353e70 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 23 Apr 2025 19:40:47 -0400 Subject: [PATCH] path_text() bugfix, replace im2scad.py with img2tex.py --- scripts/img2scad.py | 87 +++++++++++++++++++++++---- scripts/img2tex.py | 142 -------------------------------------------- shapes3d.scad | 2 +- 3 files changed, 76 insertions(+), 155 deletions(-) delete mode 100755 scripts/img2tex.py diff --git a/scripts/img2scad.py b/scripts/img2scad.py index 99cc777b..6d1e9494 100755 --- a/scripts/img2scad.py +++ b/scripts/img2scad.py @@ -6,24 +6,60 @@ import sys import os.path import argparse -from PIL import Image +from PIL import Image, ImageFilter, ImageOps -def img2scad(filename, varname, 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) - print("{} = [".format(varname), file=outf) - line = indent - for x in range(width): - line += "[ " + + if opts.range == "dynamic": + pixmin = 255; + pixmax = 0; for y in range(height): - line += "{:d}, ".format(pix[x,y]) + 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(opts.varname), file=outf) + line = indent + for y in range(height): + line += "[ " + for x in range(width): + 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 @@ -35,14 +71,38 @@ def img2scad(filename, varname, 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='img2scad') parser.add_argument('-o', '--outfile', help='Output .scad file.') parser.add_argument('-v', '--varname', help='Variable to use in .scad file.') + parser.add_argument('-i', '--invert', action='store_true', + 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() @@ -54,6 +114,9 @@ def main(): else: opts.varname = "image_data" size_pat = re.compile(r'^([0-9][0-9]*)x([0-9][0-9]*)$') + + opts.invert = bool(opts.invert) + if opts.resize: m = size_pat.match(opts.resize) if not m: @@ -67,9 +130,9 @@ def main(): if opts.outfile: with open(opts.outfile, "w") as outf: - img2scad(opts.infile, opts.varname, opts.resize, outf) + img2tex(opts.infile, opts, outf) else: - img2scad(opts.infile, opts.varname, opts.resize, sys.stdout) + img2tex(opts.infile, opts, sys.stdout) sys.exit(0) diff --git a/scripts/img2tex.py b/scripts/img2tex.py deleted file mode 100755 index 32e9915e..00000000 --- a/scripts/img2tex.py +++ /dev/null @@ -1,142 +0,0 @@ -#!env python3 - -import re -import os -import sys -import os.path -import argparse - -from PIL import Image, ImageFilter, ImageOps - - -def img2tex(filename, opts, outf): - indent = " " * 4 - im = Image.open(filename).convert('L') - 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) - - 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(opts.varname), file=outf) - line = indent - for y in range(height): - line += "[ " - for x in range(width): - 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 - line += " ]," - if line != indent: - print(line, file=outf) - line = indent - print("];", file=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', - help='Output .scad file.') - parser.add_argument('-v', '--varname', - help='Variable to use in .scad file.') - parser.add_argument('-i', '--invert', action='store_true', - 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() - - non_alnum = re.compile(r'[^a-zA-Z0-9_]') - if not opts.varname: - if opts.outfile: - opts.varname = os.path.splitext(os.path.basename(opts.outfile))[0] - opts.varname = non_alnum.sub("", opts.varname) - else: - opts.varname = "image_data" - size_pat = re.compile(r'^([0-9][0-9]*)x([0-9][0-9]*)$') - - opts.invert = bool(opts.invert) - - if opts.resize: - m = size_pat.match(opts.resize) - if not m: - print("Expected WIDTHxHEIGHT resize format.", file=sys.stderr) - sys.exit(-1) - opts.resize = (int(m.group(1)), int(m.group(2))) - - if not opts.varname or non_alnum.search(opts.varname): - print("Bad variable name: {}".format(opts.varname), file=sys.stderr) - sys.exit(-1) - - if opts.outfile: - with open(opts.outfile, "w") as outf: - img2tex(opts.infile, opts, outf) - else: - img2tex(opts.infile, opts, sys.stdout) - - sys.exit(0) - - -if __name__ == "__main__": - main() - diff --git a/shapes3d.scad b/shapes3d.scad index 5ae2e1cb..9a57bd80 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -4114,7 +4114,7 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers frame_map( x=point3d(tangent-adjustment), y=point3d(usetop ? toppts[i] : -normpts[i]) - ) left(lsize[0]/2) { + ) left(lsize[i]/2) { text(text[i], font=font, size=size, language=language, script=script); } }