From 7b898ab6ad15c20bfda6079c11b11cda1f11335c Mon Sep 17 00:00:00 2001 From: Victor Engmark Date: Tue, 19 Oct 2010 14:57:10 +0200 Subject: [PATCH] Binary logarithm possible --- README | 2 +- img2scad/img2scad.py | 74 +++++++++++++++++++++++++--------------- setup.py | 2 +- tests/example_black.png | Bin 0 -> 80 bytes tests/tests.py | 62 ++++++++++++++++++++++++++++----- 5 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 tests/example_black.png diff --git a/README b/README index 8232def..32b3483 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -img2scad.py - Convert black and white images to OpenSCAD contours. +img2scad.py - Convert black and white images to OpenSCAD structures. Installation / upgrade: sudo easy_install -U img2scad diff --git a/img2scad/img2scad.py b/img2scad/img2scad.py index 157526b..d9266f4 100755 --- a/img2scad/img2scad.py +++ b/img2scad/img2scad.py @@ -1,28 +1,34 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""img2scad - Convert images to OpenSCAD contours +"""img2scad - Convert images to OpenSCAD structures Default syntax: -img2scad [-m|--minimum=N] < input_file +img2scad [-b|--base=N] [-l|--log] < input_file Description: For each pixel in the input, it will create a cube in the output. The height of the cube corresponds to the whiteness of the corresponding pixel. -If -m or --minimum is specified, all values will be shifted by that value. This -can be positive or negative. +If -b or --base is specified, all values will be shifted by that value. This +can be positive or negative. This can be used to output zero values. + +If -l or --log is specified, the logarithm of the grey values will be used for the output. Examples: img2scad < example.png > example.scad Convert example.png to example.scad. -img2scad -m 1 < example.png > example.scad +img2scad -b 1 < example.png > example.scad Convert example.png to example.scad, with a "bedrock" of height 1. +img2scad -b 2 -l < example.png > example.scad + Convert example.png to example.scad, taking the logarithm for the height + and preserving the black pixels. + Bugs: @@ -36,6 +42,7 @@ __license__ = 'GPLv3' from getopt import getopt, GetoptError +import math from PIL import Image from signal import signal, SIGPIPE, SIG_DFL import sys @@ -58,8 +65,8 @@ """Avoid 'Broken pipe' message when canceling piped command.""" -def img2scad(stream, minimum): - """Convert black pixels to OpenSCAD cubes.""" +def img2scad(stream, base = 0, log = False): + """Convert pixels to OpenSCAD cubes.""" img = Image.open(stream) @@ -71,23 +78,33 @@ def img2scad(stream, minimum): img_matrix = img.load() - result = '' - - result += 'module topography() {\n' + result = 'module topography() {\n' result += ' union() {\n' + for row in range(height): for column in range(width): - pixel = img_matrix[column, row] + minimum - if pixel != 0: - result += ' translate([%(x)s, %(y)s, 0])' % { - 'x': BLOCK_SIZE * column - width / 2, - 'y': -BLOCK_SIZE * row + height / 2 - } - result += 'cube(' - result += '[%(block_side)s, %(block_side)s, %(height)s]);\n' % { - 'block_side': BLOCK_SIDE, - 'height': pixel - } + pixel = img_matrix[column, row] + base + + if log: + # Skip unloggable values + if pixel <= 0: + continue + pixel = round(math.log(pixel, 2), 2) + + if 0 == pixel: + continue + + # Center cubes in (x, y) plane + result += ' translate([%(x)s, %(y)s, 0])' % { + 'x': BLOCK_SIZE * column - width / 2, + 'y': -BLOCK_SIZE * row + height / 2 + } + result += 'cube(' + result += '[%(block_side)s, %(block_side)s, %(height)s]);\n' % { + 'block_side': BLOCK_SIDE, + 'height': pixel + } + result += ' }\n' result += '}\n' result += 'topography();' @@ -101,24 +118,27 @@ def main(argv = None): argv = sys.argv # Defaults - minimum = 0 + log = False + base = 0 try: opts, args = getopt( argv[1:], - 'm:', - ['minimum=']) + 'b:l', + ['base=', 'log']) except GetoptError, err: sys.stderr.write(str(err) + '\n') return 2 for option, value in opts: - if option in ('-m', '--minimum'): - minimum = int(value) + if option in ('-b', '--base'): + base = int(value) + if option in ('-l', '--log'): + log = True assert [] == args, 'There should be no arguments to this command' - result = img2scad(sys.stdin, minimum) + result = img2scad(sys.stdin, base, log) print result if __name__ == '__main__': diff --git a/setup.py b/setup.py index 8ab67d7..1e6befb 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name = 'img2scad', - version = '0.2', + version = '0.3', description = 'Image to OpenSCAD converter', long_description = module_doc, url = 'http://github.com/l0b0/img2scad', diff --git a/tests/example_black.png b/tests/example_black.png new file mode 100644 index 0000000000000000000000000000000000000000..a22cf344ea2d3b9d9aac1e64d2714a8d19a3883d GIT binary patch literal 80 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`}|}#X;^)4C~IxyaaN%JY5_^IIbrr Y0NG3ojE@xe7z0@hp00i_>zopr04l2x6#xJL literal 0 HcmV?d00001 diff --git a/tests/tests.py b/tests/tests.py index c278aea..a189c84 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -22,6 +22,7 @@ from img2scad import img2scad EXAMPLE_BIG = join(dirname(__file__), './example_big.png') +EXAMPLE_BLACK = join(dirname(__file__), './example_black.png') EXAMPLE_SMALL = join(dirname(__file__), './example_1px.png') @@ -30,24 +31,67 @@ class TestConvert(unittest.TestCase): # pylint: disable-msg=R0904 def test_small(self): - """Check that a single pixel image gives output.""" - result = img2scad.img2scad(open(EXAMPLE_SMALL), 0) + """A single pixel image gives one cube.""" + result = img2scad.img2scad(open(EXAMPLE_SMALL)) self.assertTrue(search(r'translate.*cube', result)) - def test_big(self): - """Check that a big image gives output.""" - result = img2scad.img2scad(open(EXAMPLE_BIG), 1) + def test_black(self): + """A black image gives no cubes.""" + result = img2scad.img2scad(open(EXAMPLE_BLACK)) + self.assertFalse(search(r'translate.*cube', result)) + + + def test_black_base(self): + """A black image gives output if a base is applied.""" + result = img2scad.img2scad( + open(EXAMPLE_BLACK), + base = 1) self.assertTrue(search(r'translate.*cube', result)) - def test_zero(self): - """Check that if the minimum is shifted sufficiently, the result is - empty.""" - result = img2scad.img2scad(open(EXAMPLE_SMALL), -152) + def test_shift_to_zero(self): + """A non-black pixel can be removed by the base offset.""" + result = img2scad.img2scad( + open(EXAMPLE_SMALL), + base = -152) self.assertFalse(search(r'translate.*cube', result)) + def test_log(self): + """Non-black images should be loggable.""" + result = img2scad.img2scad( + open(EXAMPLE_SMALL), + log = True) + self.assertTrue(search(r'translate.*cube', result)) + + + def test_log_one(self): + """Log of 1 is zero.""" + result = img2scad.img2scad( + open(EXAMPLE_BLACK), + base = 1, + log = True) + self.assertFalse(search(r'translate.*cube', result)) + + + def test_log_zero(self): + """Log of 0 is undefined, so we skip those.""" + result = img2scad.img2scad( + open(EXAMPLE_BLACK), + log = True) + self.assertFalse(search(r'translate.*cube', result)) + + + def test_big(self): + """Check that a big image gives output.""" + result = img2scad.img2scad( + open(EXAMPLE_BIG), + base = 5, + log = True) + self.assertTrue(search(r'translate.*cube', result)) + + class TestDoc(unittest.TestCase): """Test Python documentation strings.""" def test_doc(self):