diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc575f..58b399f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.8] - 2022-06-01 +### Added +- `INVALID_COLOR_TYPE_ERROR` error +- `COLOR_NOT_FOUND_WARNING` warning +- `BOTH_COLOR_COMPLEMENT_WARNING` warning +- `set_background` function +- `is_valid_color` function +- `color_complement` function +- `select_color` function +### Changed +- Transparent mode support for `bgcolor` parameter +- Random mode modified +- Complementary color support for `color` and `bgcolor` parameters +- `filter_color` function modified ## [0.7] - 2022-05-04 ### Added - `fill_data` function @@ -109,7 +123,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `generate` method - `nft_storage` method -[Unreleased]: https://github.com/sepandhaghighi/samila/compare/v0.7...dev +[Unreleased]: https://github.com/sepandhaghighi/samila/compare/v0.8...dev +[0.8]: https://github.com/sepandhaghighi/samila/compare/v0.7...v0.8 [0.7]: https://github.com/sepandhaghighi/samila/compare/v0.6...v0.7 [0.6]: https://github.com/sepandhaghighi/samila/compare/v0.5...v0.6 [0.5]: https://github.com/sepandhaghighi/samila/compare/v0.4...v0.5 diff --git a/README.md b/README.md index 78a6ac4..0febb45 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Samila is a generative art generator written in Python, Samila let's you create ### Source code -- Download [Version 0.7](https://github.com/sepandhaghighi/samila/archive/v0.7.zip) or [Latest Source ](https://github.com/sepandhaghighi/samila/archive/dev.zip) +- Download [Version 0.8](https://github.com/sepandhaghighi/samila/archive/v0.8.zip) or [Latest Source ](https://github.com/sepandhaghighi/samila/archive/dev.zip) - Run `pip install -r requirements.txt` or `pip3 install -r requirements.txt` (Need root access) - Run `python3 setup.py install` or `python setup.py install` (Need root access) @@ -96,7 +96,7 @@ Samila is a generative art generator written in Python, Samila let's you create - Check [Python Packaging User Guide](https://packaging.python.org/installing/) -- Run `pip install samila==0.7` or `pip3 install samila==0.7` (Need root access) +- Run `pip install samila==0.8` or `pip3 install samila==0.8` (Need root access) ### Easy install @@ -175,10 +175,14 @@ Samila is a generative art generator written in Python, Samila let's you create * Supported colors are available in `VALID_COLORS` list * `color` and `bgcolor` parameters supported formats: - 1. Color name (example: `yellow`) - 2. RGB/RGBA (example: `(0.1,0.1,0.1)`, `(0.1,0.1,0.1,0.1)`) - 3. Hex (example: `#eeefff`) - 4. Random (example: `random`) + 1. Color name (example: `color="yellow"`) + 2. RGB/RGBA (example: `color=(0.1,0.1,0.1)`, `color=(0.1,0.1,0.1,0.1)`) + 3. Hex (example: `color="#eeefff"`) + 4. Random (example: `color="random"`) + 5. Complement (example: `color="complement", bgcolor="blue"`) + 6. Transparent (example: `bgcolor="transparent"`) + +⚠️ **Transparent** mode is only available for background ### Regeneration ```pycon diff --git a/dev-requirements.txt b/dev-requirements.txt index b8b9778..a4fe8ed 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,4 @@ -matplotlib==3.5.1 +matplotlib==3.5.2 art==5.6 vulture>=1.0 bandit>=1.5.1 diff --git a/examples/demo.ipynb b/examples/demo.ipynb index 9ab48e4..8e32f40 100644 --- a/examples/demo.ipynb +++ b/examples/demo.ipynb @@ -157,10 +157,14 @@ "source": [ "* `color` and `bgcolor` parameters supported formats:\n", "\n", - " 1. Color name (example: `yellow`)\n", - " 2. RGB/RGBA (example: `(0.1,0.1,0.1)`, `(0.1,0.1,0.1,0.1)`)\n", - " 3. Hex (example: `#eeefff`)\n", - " 4. Random (example: `random`)" + " 1. Color name (example: `color=\"yellow\"`)\n", + " 2. RGB/RGBA (example: `color=(0.1,0.1,0.1)`, `color=(0.1,0.1,0.1,0.1)`)\n", + " 3. Hex (example: `color=\"#eeefff\"`)\n", + " 4. Random (example: `color=\"random\"`)\n", + " 5. Complement (example: `color=\"complement\", bgcolor=\"blue\"`)\n", + " 6. Transparent (example: `bgcolor=\"transparent\"`)\n", + "\n", + "⚠️ **Transparent** mode is only available for background" ] }, { @@ -351,4 +355,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/otherfiles/version_check.py b/otherfiles/version_check.py index 6637947..66ca7a4 100644 --- a/otherfiles/version_check.py +++ b/otherfiles/version_check.py @@ -4,7 +4,7 @@ import os import sys import codecs Failed = 0 -SAMILA_VERSION = "0.7" +SAMILA_VERSION = "0.8" SETUP_ITEMS = [ diff --git a/samila/functions.py b/samila/functions.py index c4ef5cc..139e82d 100644 --- a/samila/functions.py +++ b/samila/functions.py @@ -13,9 +13,12 @@ from .params import DEFAULT_BACKGROUND_COLOR, DEFAULT_SPOT_SIZE, DEFAULT_PROJECT from .params import Projection, VALID_COLORS, HEX_COLOR_PATTERN, NFT_STORAGE_API, NFT_STORAGE_LINK, OVERVIEW from .params import DATA_TYPE_ERROR, CONFIG_TYPE_ERROR, PLOT_DATA_ERROR, CONFIG_NO_STR_FUNCTION_ERROR from .params import NO_FIG_ERROR_MESSAGE, FIG_SAVE_SUCCESS_MESSAGE, NFT_STORAGE_SUCCESS_MESSAGE, SAVE_NO_DATA_ERROR +from .params import INVALID_COLOR_TYPE_ERROR +from .params import BOTH_COLOR_COMPLEMENT_WARNING, COLOR_NOT_FOUND_WARNING from .params import DATA_SAVE_SUCCESS_MESSAGE, SEED_LOWER_BOUND, SEED_UPPER_BOUND from .params import ELEMENTS_LIST, ARGUMENTS_LIST, OPERATORS_LIST, RANDOM_COEF_LIST from .errors import samilaDataError, samilaPlotError, samilaConfigError +from warnings import warn def random_equation_gen(): @@ -95,26 +98,114 @@ def distance_calc(s1, s2): return distances[-1] -def filter_color(color): +def is_valid_color(color): """ - Filter given color and return it. + Check that input color format is valid or not. :param color: given color - :type color: str or tuple - :return: filtered version of color + :type color: any format + :return: result as bool + """ + if color == None: + return True + try: + _ = matplotlib.colors.to_hex(color) + return True + except ValueError: + return False + + +def color_complement(color): + """ + Calculate complement color. + + :param color: given color (hex format) + :type color: str + :return: complement color (hex format) as str + """ + color = color[1:] + color = int(color, 16) + comp_color = 0xFFFFFF ^ color + comp_color = "#%06x" % comp_color + return comp_color + + +def filter_color(color, bgcolor): + """ + Filter given color and bgcolor. + + :param color: given color + :type color: any format + :param bgcolor: given background color + :type bgcolor: any format + :return: filtered version of color and bgcolor + """ + color = select_color(color) + bgcolor = select_color(bgcolor) + if color == "COMPLEMENT" and bgcolor == "COMPLEMENT": + warn(BOTH_COLOR_COMPLEMENT_WARNING, RuntimeWarning) + return None, None + if color == "COMPLEMENT": + bgcolor = matplotlib.colors.to_hex(bgcolor) + color = color_complement(bgcolor) + if bgcolor == "COMPLEMENT": + color = matplotlib.colors.to_hex(color) + bgcolor = color_complement(color) + return color, bgcolor + + +def select_color(color): + """ + Select color and return it. + + :param color: given color + :type color: any format + :return: color """ - if isinstance(color, tuple): - return color if isinstance(color, str): + if color.upper() == "TRANSPARENT": + return "TRANSPARENT" + if color.upper() == "COMPLEMENT": + return "COMPLEMENT" if color.upper() == "RANDOM": return random_hex_color_gen() if re.match(HEX_COLOR_PATTERN, color): - return color + return color.lower() distance_list = list(map(lambda x: distance_calc(color, x), VALID_COLORS)) min_distance = min(distance_list) - return VALID_COLORS[distance_list.index(min_distance)] - return None + most_similar_color = VALID_COLORS[distance_list.index(min_distance)] + if min_distance != 0: + warn( + COLOR_NOT_FOUND_WARNING.format( + color, + most_similar_color), + RuntimeWarning) + return most_similar_color + if is_valid_color(color): + return color + raise samilaPlotError(INVALID_COLOR_TYPE_ERROR) + + +def set_background(bgcolor, fig, ax): + """ + Set background for figure and axis. + + :param bgcolor: given background color + :type bgcolor: any format + :param fig: figure + :type fig: matplotlib.figure.Figure + :param ax: axis + :type ax: matplotlib.axes._subplots.AxesSubplot + :return: None + """ + if bgcolor == "TRANSPARENT": + ax.patch.set_visible(False) + fig.patch.set_visible(False) + return + fig.set_facecolor(bgcolor) + ax.set_facecolor(bgcolor) + return def filter_projection(projection): @@ -196,7 +287,7 @@ def plot_params_filter( raise samilaPlotError(PLOT_DATA_ERROR.format(1)) if g.data2 is None: raise samilaPlotError(PLOT_DATA_ERROR.format(2)) - color, bgcolor = map(filter_color, [color, bgcolor]) + color, bgcolor = filter_color(color, bgcolor) projection = filter_projection(projection) alpha = filter_float(alpha) linewidth = filter_float(linewidth) diff --git a/samila/genimage.py b/samila/genimage.py index 20d870f..ab7c23e 100644 --- a/samila/genimage.py +++ b/samila/genimage.py @@ -8,6 +8,7 @@ import matplotlib.pyplot as plt from .functions import _GI_initializer, plot_params_filter, generate_params_filter, save_params_filter 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, fill_data +from .functions import set_background from .params import * from warnings import warn @@ -126,9 +127,8 @@ class GenerativeImage: linewidth) fig = plt.figure() fig.set_size_inches(self.size[0], self.size[1]) - fig.set_facecolor(self.bgcolor) ax = fig.add_subplot(111, projection=self.projection) - ax.set_facecolor(self.bgcolor) + set_background(self.bgcolor, fig, ax) ax.scatter( self.data2, self.data1, diff --git a/samila/params.py b/samila/params.py index 4075ac8..ec07031 100644 --- a/samila/params.py +++ b/samila/params.py @@ -4,7 +4,7 @@ import math from enum import Enum from matplotlib import colors as mcolors -SAMILA_VERSION = "0.7" # pragma: no cover +SAMILA_VERSION = "0.8" # pragma: no cover OVERVIEW = ''' Samila is a generative art generator written in Python, Samila let's you @@ -39,8 +39,11 @@ CONFIG_TYPE_ERROR = "Provided config file is not supported. It should be either CONFIG_NO_STR_FUNCTION_ERROR = "Config file can't be saved. At least one of the function1_str or function2_str is None." PLOT_DATA_ERROR = "Plotting process can't be Done because data{0} is empty. Use generate method first." SAVE_NO_DATA_ERROR = "Data file can't be saved. At least one of the data1 or data2 is None." +INVALID_COLOR_TYPE_ERROR = "Given color/bgcolor type is not supported." MATPLOTLIB_VERSION_WARNING = "Source matplotlib version({0}) is different from yours, plots may be different." CALCULATION_EXCEPTION_WARNING = "The given functions are undefined at some points. Your plot may not be complete." +BOTH_COLOR_COMPLEMENT_WARNING = "It is not possible to set color and bgcolor to 'complement' at the same time! Both are automatically set to the previous or default selection." +COLOR_NOT_FOUND_WARNING = "color '{0}' not found. Replacing it with '{1}'" class Projection(Enum): @@ -68,6 +71,7 @@ RANDOM_COEF_LIST = [ "random.lognormvariate(0,1)"] ELEMENTS_LIST = [ + "{0}*math.atan({1})", "{0}*math.asinh({1})", "{0}*math.acosh(abs({1})+1)", "{0}*math.erf({1})", @@ -88,6 +92,8 @@ ARGUMENTS_LIST = [ "y-x", "x-y", "x+y", + "x**3", + "y**3", "x**2", "y**2", "(x**2)*y", @@ -97,4 +103,4 @@ ARGUMENTS_LIST = [ "x*(y**3)", "y*(x**3)"] -OPERATORS_LIST = ["+", "-", "*"] +OPERATORS_LIST = ["+", "-", "*", "/"] diff --git a/setup.py b/setup.py index 8e5e416..50076d4 100644 --- a/setup.py +++ b/setup.py @@ -32,14 +32,14 @@ def read_description(): setup( name='samila', packages=['samila'], - version='0.7', + version='0.8', description='Generative ART', long_description=read_description(), long_description_content_type='text/markdown', author='Samila Development Team', author_email='info@4r7.ir', url='https://github.com/sepandhaghighi/samila', - download_url='https://github.com/sepandhaghighi/samila/tarball/v0.7', + download_url='https://github.com/sepandhaghighi/samila/tarball/v0.8', keywords="generative-art art nft file nft-storage", project_urls={ 'Source': 'https://github.com/sepandhaghighi/samila', diff --git a/test/error_test.py b/test/error_test.py index 35995c4..fd8008f 100644 --- a/test/error_test.py +++ b/test/error_test.py @@ -34,10 +34,28 @@ samila.errors.samilaDataError: Data file can't be saved. At least one of the dat Traceback (most recent call last): ... samila.errors.samilaPlotError: Plotting process can't be Done because data2 is empty. Use generate method first. +>>> g.generate() +>>> g.plot(color=(1, 2, 3, 4, 5)) +Traceback (most recent call last): + ... +samila.errors.samilaPlotError: Given color/bgcolor type is not supported. >>> g = GenerativeImage(lambda x,y: x, lambda x,y: y) >>> result = g.save_config() Traceback (most recent call last): ... samila.errors.samilaConfigError: Config file can't be saved. At least one of the function1_str or function2_str is None. +>>> from samila.functions import * +>>> select_color(2) +Traceback (most recent call last): + ... +samila.errors.samilaPlotError: Given color/bgcolor type is not supported. +>>> filter_color(2,2) +Traceback (most recent call last): + ... +samila.errors.samilaPlotError: Given color/bgcolor type is not supported. +>>> g.plot(color=2, bgcolor=2) +Traceback (most recent call last): + ... +samila.errors.samilaPlotError: Given color/bgcolor type is not supported. >>> os.remove('data.json') """ diff --git a/test/function_test.py b/test/function_test.py index 48ba874..1cfa537 100644 --- a/test/function_test.py +++ b/test/function_test.py @@ -2,6 +2,33 @@ """ >>> import random >>> from samila.functions import * +>>> is_valid_color("blue") +True +>>> is_valid_color((0,0,0)) +True +>>> is_valid_color((0.1,0.1,0,1)) +True +>>> is_valid_color([1,1,1,1]) +True +>>> is_valid_color("#FFFAAF") +True +>>> color_complement("#FFFFFF") +'#000000' +>>> color_complement("#FFAFBF") +'#005040' +>>> color_complement("#000000") +'#ffffff' +>>> select_color("blue") +'blue' +>>> select_color("#FFFFFA") +'#fffffa' +>>> select_color((0.1,0.1,0.1)) +(0.1, 0.1, 0.1) +>>> select_color(None) +>>> select_color("complement") +'COMPLEMENT' +>>> select_color("transparent") +'TRANSPARENT' >>> s = list(float_range(1,1.5,0.1)) >>> s [1.0, 1.1, 1.2000000000000002, 1.3000000000000003, 1.4000000000000004] @@ -13,16 +40,16 @@ False False >>> is_same_data(s,[]) False ->>> filter_color("yellow") -'yellow' ->>> filter_color((0.2,0.3,0.4)) -(0.2, 0.3, 0.4) ->>> filter_color("#FFFFFF") -'#FFFFFF' +>>> filter_color("yellow", "blue") +('yellow', 'blue') +>>> filter_color((0.2,0.3,0.4), (0.2,0.3,0.4,1)) +((0.2, 0.3, 0.4), (0.2, 0.3, 0.4, 1)) +>>> filter_color("#FFFFFF", "#ffffe1") +('#ffffff', '#ffffe1') >>> random.seed(2) ->>> color1 = filter_color("random") +>>> color1, bgcolor1 = filter_color("random", "random") >>> random.seed(3) ->>> color2 = filter_color("RANDOM") +>>> color2, bgcolor2 = filter_color("RANDOM", "RANDOM") >>> color1 == color2 False >>> random.seed(2) @@ -35,8 +62,6 @@ False 7 >>> len(color2) 7 ->>> filter_color(2) ->>> filter_color(4) >>> filter_size(2) >>> filter_size((2, 'test')) >>> filter_size((2, 3.5)) diff --git a/test/overall_test.py b/test/overall_test.py index 1da3fc9..e96a30e 100644 --- a/test/overall_test.py +++ b/test/overall_test.py @@ -67,12 +67,27 @@ True >>> g.projection 'polar' >>> g.color -'#EEE245' +'#eee245' >>> g.bgcolor '#000000' >>> g.plot(projection=Projection.POLAR, color=(.1, .2, .8)) >>> g.color (0.1, 0.2, 0.8) +>>> g.plot(projection=Projection.POLAR, color="#FFFFF1", bgcolor="complement") +>>> g.color +'#fffff1' +>>> g.bgcolor +'#00000e' +>>> g.plot(projection=Projection.POLAR, color="complement", bgcolor="#AAAAAA") +>>> g.color +'#555555' +>>> g.bgcolor +'#aaaaaa' +>>> g.plot(projection=Projection.POLAR, color="complement", bgcolor="complement") +>>> g.color +'#555555' +>>> g.bgcolor +'#aaaaaa' >>> g.plot(bgcolor=(.1, .2, .8), spot_size=0.1) >>> g.plot(size=(20, 20)) >>> g.size @@ -119,7 +134,7 @@ False False >>> socket.socket = guard >>> g.generate() ->>> g.plot(color=2,bgcolor=2) +>>> g.plot() >>> result = g.nft_storage("") >>> result["status"] False @@ -146,6 +161,9 @@ True True >>> g = GenerativeImage() >>> g.generate() +>>> g.plot(color="white", bgcolor="transparent") +>>> g.bgcolor == "TRANSPARENT" +True >>> g.plot(color="white", bgcolor="black", spot_size=0.1) >>> result = g.save_config() >>> result["status"] diff --git a/test/warning_test.py b/test/warning_test.py index 99fea9a..4af1a87 100644 --- a/test/warning_test.py +++ b/test/warning_test.py @@ -23,6 +23,10 @@ True >>> g = GenerativeImage(lambda x, y: 1 / x, lambda x, y: 1 / (y - 1)) >>> with warns(RuntimeWarning, match=r"The given functions are undefined at some points. Your plot may not be complete."): ... g.generate(start=0, stop=2, step=0.1) +>>> with warns(RuntimeWarning, match=r"It is not possible to set color and bgcolor to 'complement' at the same time! Both are automatically set to the previous or default selection."): +... g.plot(color='complement', bgcolor='complement') +>>> with warns(RuntimeWarning, match=r"color 'rad' not found. Replacing it with 'red'"): +... g.plot(color='rad') >>> os.remove('data.json') >>> os.remove('config.json') """