diff --git a/README.md b/README.md index cf17bdf..1fe56bf 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,34 @@ Have a look at [this post](http://satyarth.me/articles/pixel-sorting/) or [/r/pi ### Dependencies -Python 3.6 or greater. - -Requires Pillow. `pip install Pillow` should work. If not, see [here](https://pillow.readthedocs.org/en/3.0.0/installation.html#linux-installation) for details. - -There is also a requirements file which pretty much does the same via `pip install -r requirements.txt`. +Should work in both Python 2 and 3, but Python 3 is recommended. ### Usage From the command line: ``` -git clone https://github.com/satyarth/pixelsort.git -cd pixelsort -python3 pixelsort.py %PathToImage% [options] +pip install pixelsort +python3 -m pixelsort %PathToImage% [options] ``` Tip: To replicate Kim Asendorf's original [processing script](https://github.com/kimasendorf/ASDFPixelSort), first sort vertically and then horizontally in `threshold` (default) mode: ``` -python3 pixelsort.py %PathToImage% -a 90 -python3 pixelsort.py %PathToSortedImage% +python3 -m pixelsort %PathToImage% -a 90 +python3 -m pixelsort %PathToSortedImage% +``` + +As a package: + +``` +>>> from pixelsort import pixelsort +>>> from PIL import Image +>>> a = Image.open("examples/image.jpg") +>>> a + +>>> pixelsort(a) + ``` #### Parameters: @@ -40,7 +47,7 @@ Threshold (lower) | `-t` | How dark must a pixel be to be considered as a 'bord Threshold (upper) | `-u` | How bright must a pixel be to be considered as a 'border' for sorting? Takes values from 0-1. 0.8 by default. Used in `threshold` mode. Char. length | `-c` | Characteristic length for the random width generator. Used in mode `random`. Angle | `-a` | Angle at which you're pixel sorting in degrees. `0` (horizontal) by default. -External int file | `-f` | Image used to define intervals. Must be black and white. +External interval file | `-f` | Image used to define intervals. Must be black and white. Sorting function | `-s` | Sorting function to use for sorting the pixels. Mask | `-m` | Image used for masking parts of the image. Logging Level | `-l` | Level of logging statements made visible. Choices include `DEBUG`, `INFO`, `WARNING`, `ERROR`, and `CRITICAL`. @@ -70,17 +77,17 @@ Sorting function | Description #### Examples -`python3 pixelsort.py examples/image.jpg -i random -c 20` +`python3 -m pixelsort examples/image.jpg -i random -c 20` ![random](/examples/random.png) -`python3 pixelsort.py examples/image.jpg -i edges -t 250` +`python3 -m pixelsort examples/image.jpg -i edges -t 250` ![edges](/examples/edges.png) * `file`: Intervals taken from image specified with `-f`. Must be black and white. -`python3 pixelsort.py examples/image.jpg -i file -f examples/intervals.png ` +`python3 -m pixelsort examples/image.jpg -i file -f examples/intervals.png ` ![file](/examples/intervals.png) @@ -90,13 +97,13 @@ Sorting function | Description * `mask`: Mask taken from image specified with `-m`. Must be black and white. -`python3 pixelsort.py examples/image.jpg -i random -c 20 -m examples/mask.png` +`python3 -m pixelsort examples/image.jpg -i random -c 20 -m examples/mask.png` ![file](/examples/mask.png) ![file](/examples/masked.png) -### todo +### Todo * Allow defining different intervals for different channels. diff --git a/argparams.py b/argparams.py deleted file mode 100644 index 863fe14..0000000 --- a/argparams.py +++ /dev/null @@ -1,104 +0,0 @@ -import argparse -import util -import interval -import sorting -import logging - - -def read_interval_function(int_function): - try: - return { - "random": interval.random, - "threshold": interval.threshold, - "edges": interval.edge, - "waves": interval.waves, - "file": interval.file_mask, - "file-edges": interval.file_edges, - "none": interval.none - }[int_function] - except KeyError: - logging.warning( - "Invalid interval function specified, defaulting to 'threshold'.") - return interval.threshold - - -def read_sorting_function(sorting_function): - try: - return { - "lightness": sorting.lightness, - "hue": sorting.hue, - "intensity": sorting.intensity, - "minimum": sorting.minimum, - "saturation": sorting.saturation - }[sorting_function] - except KeyError: - logging.warning( - "Invalid sorting function specified, defaulting to 'lightness'.") - return sorting.lightness - - -def parse_args(): - p = argparse.ArgumentParser(description="pixel mangle an image") - p.add_argument("image", help="input image file") - p.add_argument("-o", "--output", help="output image file, defaults to a randomly generated string", - default=util.id_generator() + ".png") - p.add_argument("-i", "--int_function", - help="random, threshold, edges, waves, file, file-edges, none", default="threshold") - p.add_argument("-f", "--int_file", - help="Image used for defining intervals", default="in.png") - p.add_argument("-t", "--threshold", type=float, - help="Pixels darker than this are not sorted, between 0 and 1", default=0.25) - p.add_argument("-u", "--upper_threshold", type=float, - help="Pixels brighter than this are not sorted, between 0 and 1", default=0.8) - p.add_argument("-c", "--clength", type=int, - help="Characteristic length of random intervals", default=50) - p.add_argument("-a", "--angle", type=float, - help="Rotate the image by an angle (in degrees) before sorting", default=0) - p.add_argument("-r", "--randomness", type=float, - help="What percentage of intervals are NOT sorted", default=0) - p.add_argument("-s", "--sorting_function", - help="lightness, intensity, hue, saturation, minimum", default="lightness") - p.add_argument("-m", "--mask", - help="Image used for masking parts of the image") - p.add_argument("-l", "--log_level", default="WARNING", help="Print more or less info", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) - - __args = p.parse_args() - - logging.basicConfig( - format="%(name)s: %(levelname)s - %(message)s", level=logging.getLevelName(__args.log_level)) - - return { - "image_input_path": __args.image, - "output_image_path": __args.output, - "interval_function": __args.int_function, - "interval_file_path": __args.int_file, - "bottom_threshold": __args.threshold, - "upper_threshold": __args.upper_threshold, - "clength": __args.clength, - "angle": __args.angle, - "randomness": __args.randomness, - "sorting_function": __args.sorting_function, - "mask": __args.mask - } - - -def verify_args(args): - # Informational logs - logging.info("Interval function: {}".format(args['interval_function'])) - if args["interval_function"] in ["threshold", "edges", "file-edges"]: - logging.info("Lower threshold: {}".format(args['bottom_threshold'])) - if args["interval_function"] == "threshold": - logging.info("Upper threshold: {}".format(args['upper_threshold'])) - if args["interval_function"] in ["random", "waves"]: - logging.info("Characteristic length: {}".format(args['clength'])) - logging.info("Randomness: {}".format(args['randomness'])) - # Actual validation - if not args["output_image_path"]: - output = "{}.png".format(util.id_generator()) - logging.warning( - "No output path provided, defaulting to {}".format(output)) - args["output_image_path"] = output - args["interval_function"] = read_interval_function( - args["interval_function"]) - args["sorting_function"] = read_sorting_function(args["sorting_function"]) diff --git a/constants.py b/constants.py deleted file mode 100644 index 28d24d0..0000000 --- a/constants.py +++ /dev/null @@ -1,2 +0,0 @@ -black_pixel = (0, 0, 0, 255) -white_pixel = (255, 255, 255, 255) diff --git a/pixelsort.py b/pixelsort.py deleted file mode 100644 index 40e1d88..0000000 --- a/pixelsort.py +++ /dev/null @@ -1,79 +0,0 @@ -try: - import Image -except ImportError: - from PIL import Image -from sorter import sort_image -from argparams import parse_args, verify_args -import util -import constants -import logging - - -def main(args): - verify_args(args) - - logging.debug("Opening image...") - input_img = Image.open(args["image_input_path"]) - - logging.debug("Converting to RGBA...") - input_img.convert('RGBA') - - logging.debug("Rotating image...") - input_img = input_img.rotate(args["angle"], expand=True) - - logging.debug("Getting data...") - data = input_img.load() - - logging.debug("Loading mask...") - mask = Image.open(args["mask"]).convert('RGBA').rotate(args["angle"], expand=True).load() if args["mask"] else None - - logging.debug("Getting pixels...") - pixels = get_pixels(data, mask, input_img.size) - - logging.debug("Determining intervals...") - intervals = args["interval_function"](pixels, args) - - logging.debug("Sorting pixels...") - sorted_pixels = sort_image( - pixels, intervals, args["randomness"], args["sorting_function"]) - - logging.debug("Placing pixels in output image...") - output_img = place_pixels(sorted_pixels, mask, data, input_img.size) - - if args["angle"] != 0: - logging.debug("Rotating output image back to original orientation...") - output_img = output_img.rotate(-args["angle"], expand=True) - - logging.debug("Crop image to apropriate size...") - output_img = util.crop_to( - output_img, Image.open(args["image_input_path"])) - - logging.debug("Saving image...") - output_img.save(args["output_image_path"]) - - -def get_pixels(data, mask, size): - pixels = [] - for y in range(size[1]): - pixels.append([]) - for x in range(size[0]): - if not (mask and mask[x, y] == constants.black_pixel): - pixels[y].append(data[x, y]) - return pixels - - -def place_pixels(pixels, mask, original, size): - output_img = Image.new('RGBA', size) - for y in range(size[1]): - count = 0 - for x in range(size[0]): - if mask and mask[x, y] == constants.black_pixel: - output_img.putpixel((x, y), original[x, y]) - else: - output_img.putpixel((x, y), pixels[y][count]) - count += 1 - return output_img - - -if __name__ == "__main__": - main(parse_args()) diff --git a/pixelsort/__init__.py b/pixelsort/__init__.py new file mode 100644 index 0000000..17eaeee --- /dev/null +++ b/pixelsort/__init__.py @@ -0,0 +1,2 @@ +from pixelsort.main import pixelsort +NAME = "pixelsort" diff --git a/pixelsort/__main__.py b/pixelsort/__main__.py new file mode 100644 index 0000000..8f87fdc --- /dev/null +++ b/pixelsort/__main__.py @@ -0,0 +1,27 @@ +from PIL import Image +import logging +from pixelsort.argparams import parse_args +from pixelsort.main import pixelsort +from pixelsort.util import id_generator + +args = parse_args() +image_input_path = args.pop("image_input_path") +image_output_path = args.pop("image_output_path") +interval_file_path = args.pop("interval_file_path") +mask_path = args.pop("mask_path") + +if image_output_path is None: + image_output_path = id_generator() + ".png" + logging.warning("No output path provided, using " + image_output_path) + +logging.debug("Opening image...") +args["image"] = Image.open(image_input_path) +if mask_path: + logging.debug("Opening mask...") + args["mask_image"] = Image.open(mask_path) +if interval_file_path: + logging.debug("Opening interval file...") + args["interval_file"] = Image.open(interval_file_path) + +logging.debug("Saving image...") +pixelsort(**args).save(image_output_path) diff --git a/pixelsort/argparams.py b/pixelsort/argparams.py new file mode 100644 index 0000000..f861564 --- /dev/null +++ b/pixelsort/argparams.py @@ -0,0 +1,55 @@ +import argparse +import logging +from pixelsort.interval import choices as interval_choices +from pixelsort.sorting import choices as sorting_choices +from pixelsort.constants import DEFAULTS + + +def parse_args(): + parser = argparse.ArgumentParser(description="Pixel mangle an image.") + parser.add_argument("image", help="Input image file path.") + parser.add_argument("-o", "--output", + help="Output image file path, DEFAULTS to the time created.") + parser.add_argument("-i", "--int_function", + choices=interval_choices.keys(), + default=DEFAULTS["interval_function"], + help="Function to determine sorting intervals") + parser.add_argument("-f", "--int_file", + help="Image used for defining intervals.") + parser.add_argument("-t", "--threshold", type=float, default=DEFAULTS["lower_threshold"], + help="Pixels darker than this are not sorted, between 0 and 1") + parser.add_argument("-u", "--upper_threshold", type=float, default=DEFAULTS["upper_threshold"], + help="Pixels brighter than this are not sorted, between 0 and 1") + parser.add_argument("-c", "--clength", type=int, default=DEFAULTS["clength"], + help="Characteristic length of random intervals") + parser.add_argument("-a", "--angle", type=float, default=DEFAULTS["angle"], + help="Rotate the image by an angle (in degrees) before sorting") + parser.add_argument("-r", "--randomness", type=float, default=DEFAULTS["randomness"], + help="What percentage of intervals are NOT sorted") + parser.add_argument("-s", "--sorting_function", + choices=sorting_choices.keys(), + default=DEFAULTS["sorting_function"], + help="Function to sort pixels by.") + parser.add_argument( + "-m", "--mask", help="Image used for masking parts of the image") + parser.add_argument("-l", "--log_level", default="WARNING", help="Print more or less info", + choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) + + _args = parser.parse_args() + + logging.basicConfig( + format="%(name)s: %(levelname)s - %(message)s", level=logging.getLevelName(_args.log_level)) + + return { + "image_input_path": _args.image, + "image_output_path": _args.output, + "interval_function": _args.int_function, + "interval_file_path": _args.int_file, + "lower_threshold": _args.threshold, + "upper_threshold": _args.upper_threshold, + "clength": _args.clength, + "angle": _args.angle, + "randomness": _args.randomness, + "sorting_function": _args.sorting_function, + "mask_path": _args.mask + } diff --git a/pixelsort/constants.py b/pixelsort/constants.py new file mode 100644 index 0000000..47ba28a --- /dev/null +++ b/pixelsort/constants.py @@ -0,0 +1,11 @@ +BLACK_PIXEL = (0, 0, 0, 255) +WHITE_PIXEL = (255, 255, 255, 255) +DEFAULTS = { + "interval_function": "threshold", + "lower_threshold": 0.25, + "upper_threshold": 0.8, + "clength": 50, + "angle": 0, + "randomness": 0, + "sorting_function": "lightness", +} diff --git a/interval.py b/pixelsort/interval.py similarity index 57% rename from interval.py rename to pixelsort/interval.py index a155682..177e44e 100644 --- a/interval.py +++ b/pixelsort/interval.py @@ -1,17 +1,12 @@ -try: - import Image - import ImageFilter -except ImportError: - from PIL import Image, ImageFilter -import random as rand -import constants -import util import logging +from random import randint +from PIL import ImageFilter +from pixelsort.constants import BLACK_PIXEL, WHITE_PIXEL +from pixelsort.util import lightness, random_width -def edge(pixels, args): - img = Image.open(args["image_input_path"]) - img = img.rotate(args["angle"], expand=True) - edges = img.filter(ImageFilter.FIND_EDGES) + +def edge(pixels, image, lower_threshold, **kwargs): + edges = image.filter(ImageFilter.FIND_EDGES) edges = edges.convert('RGBA') edge_data = edges.load() @@ -20,50 +15,50 @@ def edge(pixels, args): intervals = [] logging.debug("Defining edges...") - for y in range(img.size[1]): + for y in range(image.size[1]): filter_pixels.append([]) - for x in range(img.size[0]): + for x in range(image.size[0]): filter_pixels[y].append(edge_data[x, y]) logging.debug("Thresholding...") for y in range(len(pixels)): edge_pixels.append([]) for x in range(len(pixels[y])): - if util.lightness(filter_pixels[y][x]) < args["bottom_threshold"]: - edge_pixels[y].append(constants.white_pixel) + if lightness(filter_pixels[y][x]) < lower_threshold: + edge_pixels[y].append(WHITE_PIXEL) else: - edge_pixels[y].append(constants.black_pixel) + edge_pixels[y].append(BLACK_PIXEL) logging.debug("Cleaning up edges...") for y in range(len(pixels) - 1, 1, -1): for x in range(len(pixels[y]) - 1, 1, -1): - if edge_pixels[y][x] == constants.black_pixel and edge_pixels[y][x - 1] == constants.black_pixel: - edge_pixels[y][x] = constants.white_pixel + if edge_pixels[y][x] == BLACK_PIXEL and edge_pixels[y][x - 1] == BLACK_PIXEL: + edge_pixels[y][x] = WHITE_PIXEL logging.debug("Defining intervals...") for y in range(len(pixels)): intervals.append([]) for x in range(len(pixels[y])): - if edge_pixels[y][x] == constants.black_pixel: + if edge_pixels[y][x] == BLACK_PIXEL: intervals[y].append(x) intervals[y].append(len(pixels[y])) return intervals -def threshold(pixels, args): +def threshold(pixels, lower_threshold, upper_threshold, **kwargs): intervals = [] logging.debug("Defining intervals...") for y in range(len(pixels)): intervals.append([]) for x in range(len(pixels[y])): - if util.lightness(pixels[y][x]) < args["bottom_threshold"] or util.lightness(pixels[y][x]) > args["upper_threshold"]: + if lightness(pixels[y][x]) < lower_threshold or lightness(pixels[y][x]) > upper_threshold: intervals[y].append(x) intervals[y].append(len(pixels[y])) return intervals -def random(pixels, args): +def random(pixels, clength, **kwargs): intervals = [] logging.debug("Defining intervals...") @@ -71,7 +66,7 @@ def random(pixels, args): intervals.append([]) x = 0 while True: - width = util.random_width(args["clength"]) + width = random_width(clength) x += width if x > len(pixels[y]): intervals[y].append(len(pixels[y])) @@ -81,7 +76,7 @@ def random(pixels, args): return intervals -def waves(pixels, args): +def waves(pixels, clength, **kwargs): intervals = [] logging.debug("Defining intervals...") @@ -89,7 +84,7 @@ def waves(pixels, args): intervals.append([]) x = 0 while True: - width = args["clength"] + rand.randint(0, 10) + width = clength + randint(0, 10) x += width if x > len(pixels[y]): intervals[y].append(len(pixels[y])) @@ -99,37 +94,35 @@ def waves(pixels, args): return intervals -def file_mask(pixels, args): +def file_mask(pixels, interval_image, **kwargs): intervals = [] file_pixels = [] - img = load_interval_file(args) - data = img.load() - for y in range(img.size[1]): + data = interval_image.load() + for y in range(interval_image.size[1]): file_pixels.append([]) - for x in range(img.size[0]): + for x in range(interval_image.size[0]): file_pixels[y].append(data[x, y]) logging.debug("Cleaning up edges...") for y in range(len(pixels) - 1, 1, -1): for x in range(len(pixels[y]) - 1, 1, -1): - if file_pixels[y][x] == constants.black_pixel and file_pixels[y][x - 1] == constants.black_pixel: - file_pixels[y][x] = constants.white_pixel + if file_pixels[y][x] == BLACK_PIXEL and file_pixels[y][x - 1] == BLACK_PIXEL: + file_pixels[y][x] = WHITE_PIXEL logging.debug("Defining intervals...") for y in range(len(pixels)): intervals.append([]) for x in range(len(pixels[y])): - if file_pixels[y][x] == constants.black_pixel: + if file_pixels[y][x] == BLACK_PIXEL: intervals[y].append(x) intervals[y].append(len(pixels[y])) return intervals -def file_edges(pixels, args): - img = load_interval_file(args) - edges = img.filter(ImageFilter.FIND_EDGES) +def file_edges(pixels, interval_image, lower_threshold, **kwargs): + edges = interval_image.filter(ImageFilter.FIND_EDGES) edges = edges.convert('RGBA') edge_data = edges.load() @@ -138,47 +131,49 @@ def file_edges(pixels, args): intervals = [] logging.debug("Defining edges...") - for y in range(img.size[1]): + for y in range(interval_image.size[1]): filter_pixels.append([]) - for x in range(img.size[0]): + for x in range(interval_image.size[0]): filter_pixels[y].append(edge_data[x, y]) logging.debug("Thresholding...") for y in range(len(pixels)): edge_pixels.append([]) for x in range(len(pixels[y])): - if util.lightness(filter_pixels[y][x]) < args["bottom_threshold"]: - edge_pixels[y].append(constants.white_pixel) + if lightness(filter_pixels[y][x]) < lower_threshold: + edge_pixels[y].append(WHITE_PIXEL) else: - edge_pixels[y].append(constants.black_pixel) + edge_pixels[y].append(BLACK_PIXEL) logging.debug("Cleaning up edges...") for y in range(len(pixels) - 1, 1, -1): for x in range(len(pixels[y]) - 1, 1, -1): - if edge_pixels[y][x] == constants.black_pixel and edge_pixels[y][x - 1] == constants.black_pixel: - edge_pixels[y][x] = constants.white_pixel + if edge_pixels[y][x] == BLACK_PIXEL and edge_pixels[y][x - 1] == BLACK_PIXEL: + edge_pixels[y][x] = WHITE_PIXEL logging.debug("Defining intervals...") for y in range(len(pixels)): intervals.append([]) for x in range(len(pixels[y])): - if edge_pixels[y][x] == constants.black_pixel: + if edge_pixels[y][x] == BLACK_PIXEL: intervals[y].append(x) intervals[y].append(len(pixels[y])) return intervals -def none(pixels, args): +def none(pixels, **kwargs): intervals = [] for y in range(len(pixels)): intervals.append([len(pixels[y])]) return intervals -# Helper functions -def load_interval_file(args): - img = Image.open(args["interval_file_path"]) - img = img.convert('RGBA') - img = img.rotate(args["angle"], expand=True) - img = img.resize(Image.open(args["image_input_path"]).size, Image.ANTIALIAS) - return img \ No newline at end of file +choices = { + "random": random, + "threshold": threshold, + "edges": edge, + "waves": waves, + "file": file_mask, + "file-edges": file_edges, + "none": none +} diff --git a/pixelsort/main.py b/pixelsort/main.py new file mode 100644 index 0000000..bfd8793 --- /dev/null +++ b/pixelsort/main.py @@ -0,0 +1,108 @@ +import logging +from PIL import Image + +from pixelsort.util import crop_to +from pixelsort.sorter import sort_image +from pixelsort.constants import DEFAULTS, BLACK_PIXEL +from pixelsort.interval import choices as interval_choices +from pixelsort.sorting import choices as sorting_choices + + +def pixelsort( + image, + mask_image=None, + interval_image=None, + randomness=DEFAULTS["randomness"], + clength=DEFAULTS["clength"], + sorting_function=DEFAULTS["sorting_function"], + interval_function=DEFAULTS["interval_function"], + lower_threshold=DEFAULTS["lower_threshold"], + upper_threshold=DEFAULTS["upper_threshold"], + angle=DEFAULTS["angle"] +): + + logging.debug("Cleaning input image...") + original = image + image = image.convert('RGBA').rotate(angle, expand=True) + + logging.debug("Getting data...") + input_data = image.load() + + mask_data = None + if mask_image: + logging.debug("Loading mask...") + mask_data = (mask_image + .convert('RGBA') + .rotate(angle, expand=True) + .resize(image.size, Image.ANTIALIAS).load()) + + if interval_image: + logging.debug("Loading interval image...") + (interval_image + .convert('RGBA') + .rotate(angle, expand=True) + .resize(image.size, Image.ANTIALIAS)) + + logging.debug("Getting pixels...") + pixels = _get_pixels(input_data, mask_data, image.size) + + logging.debug("Determining intervals...") + try: + interval_function = interval_choices[interval_function] + except KeyError: + logging.warning( + "Invalid interval function specified, defaulting to 'threshold'.") + interval_function = interval_choices["threshold"] + intervals = interval_function( + pixels, + lower_threshold=lower_threshold, + upper_threshold=upper_threshold, + clength=clength, + interval_image=interval_image, + image=image + ) + + logging.debug("Sorting pixels...") + try: + sorting_function = sorting_choices[sorting_function] + except KeyError: + logging.warning( + "Invalid sorting function specified, defaulting to 'lightness'.") + sorting_function = sorting_choices["lightness"] + sorted_pixels = sort_image(pixels, intervals, randomness, sorting_function) + + logging.debug("Placing pixels in output image...") + output_img = _place_pixels( + sorted_pixels, mask_data, input_data, image.size) + + if angle != 0: + logging.debug("Rotating output image back to original orientation...") + output_img = output_img.rotate(-angle, expand=True) + + logging.debug("Crop image to appropriate size...") + output_img = crop_to(output_img, original) + + return output_img + + +def _get_pixels(data, mask, size): + pixels = [] + for y in range(size[1]): + pixels.append([]) + for x in range(size[0]): + if not (mask and mask[x, y] == BLACK_PIXEL): + pixels[y].append(data[x, y]) + return pixels + + +def _place_pixels(pixels, mask, original, size): + output_img = Image.new('RGBA', size) + for y in range(size[1]): + count = 0 + for x in range(size[0]): + if mask and mask[x, y] == BLACK_PIXEL: + output_img.putpixel((x, y), original[x, y]) + else: + output_img.putpixel((x, y), pixels[y][count]) + count += 1 + return output_img diff --git a/sorter.py b/pixelsort/sorter.py similarity index 92% rename from sorter.py rename to pixelsort/sorter.py index fe977d8..65fed9a 100644 --- a/sorter.py +++ b/pixelsort/sorter.py @@ -10,7 +10,7 @@ def sort_image(pixels, intervals, randomness, sorting_function): interval = [] for x in range(x_min, x_max): interval.append(pixels[y][x]) - if random.randint(0, 100) >= randomness: + if random.randint(0, 100) > randomness: row += sort_interval(interval, sorting_function) else: row += interval diff --git a/sorting.py b/pixelsort/sorting.py similarity index 63% rename from sorting.py rename to pixelsort/sorting.py index e5f963b..f79a9d6 100644 --- a/sorting.py +++ b/pixelsort/sorting.py @@ -1,4 +1,4 @@ -import util +import pixelsort.util as util def lightness(pixel): @@ -19,3 +19,12 @@ def saturation(pixel): def minimum(pixel): return min(pixel[0], pixel[1], pixel[2]) + + +choices = { + "lightness": lightness, + "hue": hue, + "intensity": intensity, + "minimum": minimum, + "saturation": saturation +} diff --git a/util.py b/pixelsort/util.py similarity index 100% rename from util.py rename to pixelsort/util.py index 54fa77c..6b9483e 100644 --- a/util.py +++ b/pixelsort/util.py @@ -1,6 +1,6 @@ -import random import string from colorsys import rgb_to_hsv +import random def id_generator(size=5, chars=string.ascii_lowercase + string.ascii_uppercase + string.digits): diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f332f8c --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +with open('requirements.txt') as fh: + requirements = fh.read().splitlines() + +setuptools.setup( + name="pixelsort", + version="1.0.0", + author="Bernard Zhao", + author_email="bernardzhao@berkeley.edu", + description="An image pixelsorter for Python.", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/satyarth/pixelsort", + packages=setuptools.find_packages(), + install_requires=requirements, + classifiers=[ + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ] +)