diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ef25f..5fd10bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `RANDOM_EQUATION_MIN_COMPLEXITY` parameter - `RANDOM_EQUATION_FOF_MAX_DEPTH` parameter - `RANDOM_EQUATION_FOF_MIN_DEPTH` parameter +- `rotate` function ### Changed +- `rotation` parameter added to `plot` method - `load_config` function modified - Random mode modified - `RANDOM_EQUATION_GEN_COMPLEXITY` parameter renamed to `RANDOM_EQUATION_MAX_COMPLEXITY` diff --git a/README.md b/README.md index 92f4b02..f1e1863 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,17 @@ Samila is a generative art generator written in Python, Samila lets you create i * Supported markers : `POINT`, `PIXEL`, `CIRCLE`, `TRIANGLE_DOWN`, `TRIANGLE_UP`, `TRIANGLE_LEFT`, `TRIANGLE_RIGHT`, `TRI_DOWN`, `TRI_UP`, `TRI_LEFT`, `TRI_RIGHT`, `OCTAGON`, `SQUARE`, `PENTAGON`, `PLUS`, `PLUS_FILLED`, `STAR`, `HEXAGON_VERTICAL`, `HEXAGON_HORIZONTAL`, `X`, `X_FILLED`, `DIAMOND`, `DIAMON_THIN`, `VLINE`, `HLINE` and `RANDOM` * Default marker is `POINT` +### Rotation +You can even rotate your art by using `rotation` parameter. Enter your desired rotation for the image in degrees and you will have it. + +```pycon +>>> g = GenerativeImage(f1, f2) +>>> g.generate() +>>> g.plot(rotation=45) +``` + +* Default rotation is 0. + ### Range ```pycon >>> g = GenerativeImage(f1, f2) diff --git a/examples/demo.ipynb b/examples/demo.ipynb index b8c1a06..ac33b5f 100644 --- a/examples/demo.ipynb +++ b/examples/demo.ipynb @@ -133,6 +133,28 @@ " plt.close()" ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Rotation\n", + "You can even rotate your art by using `rotation` parameter. Enter your desired rotation for the image in degrees and you will have it.\n", + "\n", + "* Default rotation is 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g = GenerativeImage(f1, f2)\n", + "g.generate()\n", + "g.plot(rotation=45)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/requirements.txt b/requirements.txt index 4ddff1c..f92e40e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ matplotlib>=3.0.0 requests>=2.20.0 art>=1.8 +Pillow>=6.2 diff --git a/samila/functions.py b/samila/functions.py index 5e74213..7f22a75 100644 --- a/samila/functions.py +++ b/samila/functions.py @@ -11,8 +11,9 @@ import random import matplotlib from matplotlib import cm from matplotlib.colors import ListedColormap +from PIL import Image from .params import DEFAULT_MARKER, DEFAULT_START, DEFAULT_STOP, DEFAULT_STEP, DEFAULT_COLOR, DEFAULT_IMAGE_SIZE, DEFAULT_DEPTH -from .params import DEFAULT_CMAP, DEFAULT_CMAP_RANGE +from .params import DEFAULT_CMAP, DEFAULT_CMAP_RANGE, DEFAULT_ROTATION from .params import DEFAULT_BACKGROUND_COLOR, DEFAULT_SPOT_SIZE, DEFAULT_PROJECTION, DEFAULT_ALPHA, DEFAULT_LINEWIDTH from .params import Projection, Marker, VALID_COLORS, HEX_COLOR_PATTERN, NFT_STORAGE_API, NFT_STORAGE_LINK, OVERVIEW from .params import DATA_TYPE_ERROR, DATA_FORMAT_ERROR, CONFIG_TYPE_ERROR, CONFIG_FORMAT_ERROR, PLOT_DATA_ERROR, CONFIG_NO_STR_FUNCTION_ERROR @@ -307,6 +308,30 @@ def filter_size(size): return None +def rotate(fig, ax, rotation): + """ + Rotate the given figure and return axis. + + :param fig: figure containing the image + :type fig: Figure + :param ax: axis on which rotated image is ploted + :type ax: Axis + :param rotation: desired rotation (in degrees) + :type rotation: float + :return: axis containing rotated image + """ + if rotation != DEFAULT_ROTATION: + buf = io.BytesIO() + fig.savefig(buf, format='png') + ax.cla() + with Image.open(buf) as im: + ax.imshow(im.rotate(rotation)) + ax.set_axis_off() + ax.patch.set_zorder(-1) + ax.add_artist(ax.patch) + return ax + + def plot_params_filter( g, color=None, @@ -317,7 +342,8 @@ def plot_params_filter( projection=None, marker=None, alpha=None, - linewidth=None): + linewidth=None, + rotation=None): """ Filter plot method parameters. @@ -341,6 +367,8 @@ def plot_params_filter( :type alpha: float :param linewidth: width of line :type linewidth: float + :param rotation: desired rotation (in degrees) + :type rotation: float :return: None """ if g.data1 is None: @@ -378,8 +406,10 @@ def plot_params_filter( alpha = g.alpha if linewidth is None: linewidth = g.linewidth - g.color, g.bgcolor, g.cmap, g.spot_size, g.size, g.projection, g.marker, g.alpha, g.linewidth = \ - color, bgcolor, cmap, spot_size, size, projection, marker, alpha, linewidth + if rotation is None: + rotation = g.rotation + g.color, g.bgcolor, g.cmap, g.spot_size, g.size, g.projection, g.marker, g.alpha, g.linewidth, g.rotation = \ + color, bgcolor, cmap, spot_size, size, projection, marker, alpha, linewidth, rotation def generate_params_filter( @@ -464,6 +494,7 @@ def _GI_initializer(g, function1, function2): g.marker = DEFAULT_MARKER g.alpha = DEFAULT_ALPHA g.linewidth = DEFAULT_LINEWIDTH + g.rotation = DEFAULT_ROTATION g.depth = DEFAULT_DEPTH g.missed_points_number = 0 @@ -558,7 +589,8 @@ def get_data(g): "marker": g.marker, "alpha": g.alpha, "linewidth": g.linewidth, - "depth": g.depth + "depth": g.depth, + "rotation": g.rotation, } data['matplotlib_version'] = matplotlib_version data['python_version'] = python_version @@ -595,7 +627,8 @@ def get_config(g): "marker": g.marker, "alpha": g.alpha, "linewidth": g.linewidth, - "depth": g.depth + "depth": g.depth, + "rotation": g.rotation, } config['matplotlib_version'] = matplotlib_version config['python_version'] = python_version @@ -783,6 +816,7 @@ def load_data(g, data): g.alpha = plot_config.get("alpha", DEFAULT_ALPHA) g.linewidth = plot_config.get("linewidth", DEFAULT_LINEWIDTH) g.depth = plot_config.get("depth", DEFAULT_DEPTH) + g.rotation = plot_config.get("rotation", DEFAULT_ROTATION) return raise samilaDataError(DATA_TYPE_ERROR) @@ -824,5 +858,6 @@ def load_config(g, config): g.alpha = plot_config.get("alpha", DEFAULT_ALPHA) g.linewidth = plot_config.get("linewidth", DEFAULT_LINEWIDTH) g.depth = plot_config.get("depth", DEFAULT_DEPTH) + g.rotation = plot_config.get("rotation", DEFAULT_ROTATION) return raise samilaConfigError(CONFIG_TYPE_ERROR) diff --git a/samila/genimage.py b/samila/genimage.py index 94c6ca8..9a2c2ac 100644 --- a/samila/genimage.py +++ b/samila/genimage.py @@ -10,7 +10,7 @@ from .functions import _GI_initializer, plot_params_filter, generate_params_filt from .functions import get_config, get_data, get_python_version from .functions import float_range, save_data_file, save_fig_file, save_fig_buf, save_config_file from .functions import load_data, load_config, random_equation_gen, nft_storage_upload -from .functions import set_background +from .functions import set_background, rotate from .params import * from warnings import warn, catch_warnings, simplefilter @@ -107,7 +107,8 @@ class GenerativeImage: projection=None, marker=None, alpha=None, - linewidth=None): + linewidth=None, + rotation=None): """ Plot the generated art. @@ -129,6 +130,8 @@ class GenerativeImage: :type alpha: float :param linewidth: width of line :type linewidth: float + :param rotation: desired rotation (in degrees) + :type rotation: float :return: None """ plot_params_filter( @@ -141,7 +144,8 @@ class GenerativeImage: projection, marker, alpha, - linewidth) + linewidth, + rotation) fig = plt.figure() fig.set_size_inches(self.size[0], self.size[1]) ax = fig.add_subplot(111, projection=self.projection) @@ -160,6 +164,7 @@ class GenerativeImage: ax.set_axis_off() ax.patch.set_zorder(-1) ax.add_artist(ax.patch) + ax = rotate(fig, ax, self.rotation) self.fig = fig def nft_storage( diff --git a/samila/params.py b/samila/params.py index a988c2e..193f53f 100644 --- a/samila/params.py +++ b/samila/params.py @@ -26,6 +26,7 @@ DEFAULT_LINEWIDTH = 1.5 DEFAULT_IMAGE_SIZE = (10, 10) DEFAULT_SPOT_SIZE = 0.01 DEFAULT_DEPTH = 1 +DEFAULT_ROTATION = 0.0 DEFAULT_PROJECTION = "rectilinear" DEFAULT_MARKER = "." SEED_LOWER_BOUND = 0 diff --git a/test/overall_test.py b/test/overall_test.py index 788bff4..8e02a26 100644 --- a/test/overall_test.py +++ b/test/overall_test.py @@ -97,6 +97,9 @@ True 'x' >>> g.spot_size 100 +>>> g.plot(rotation=45) +>>> int(g.rotation) +45 >>> g.plot(bgcolor=(.1, .2, .8), spot_size=0.1) >>> g.plot(size=(20, 20)) >>> g.size @@ -220,6 +223,8 @@ True True >>> g.marker == g_.marker True +>>> g.rotation == g_.rotation +True >>> g.alpha == g_.alpha True >>> g.linewidth == g_.linewidth @@ -254,6 +259,8 @@ True True >>> g.marker == g_.marker True +>>> g.rotation == g_.rotation +True >>> g.alpha == g_.alpha True >>> g.linewidth == g_.linewidth