shades.shades API documentation

# shades.shades

shades

contains classes and functions relating to Shades' shade object

View Source

      1"""
      2shades
      3
      4contains classes and functions relating to Shades' shade object
      5"""
      6from abc import ABC, abstractmethod
      7from typing import Tuple, List
      8
      9import numpy as np
     10
     11from PIL import Image
     12
     13from .noise_fields import NoiseField, noise_fields
     14from .utils import color_clamp
     15
     16
     17class Shade(ABC):
     18    """
     19    An Abstract base clase Shade.
     20    Methods are used to mark shapes onto images according to various color rules.
     21
     22    Initialisation parameters of warp_noise takes two noise_fields affecting how
     23    much a point is moved across x and y axis.
     24
     25    warp_size determines the amount that a warp_noise result of 1 (maximum perlin
     26    value) translates as
     27    """
     28
     29    def __init__(
     30            self,
     31            color: Tuple[int, int, int] = (0, 0, 0),
     32            warp_noise: Tuple[NoiseField] = noise_fields(channels=2),
     33            warp_size: float = 0,
     34        ):
     35        self.color = color
     36        self.warp_noise = warp_noise
     37        self.warp_size = warp_size
     38
     39    @abstractmethod
     40    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
     41        """
     42        Determines the shade/color for given xy coordinate.
     43        """
     44
     45    def adjust_point(self, xy_coords: Tuple[int, int]) -> Tuple[int, int]:
     46        """
     47        If warp is applied in shade, appropriately adjusts location of point.
     48        """
     49        if self.warp_size == 0:
     50            return xy_coords
     51        x_coord = xy_coords[0] + (self.warp_noise[0].noise(xy_coords) * self.warp_size)
     52        y_coord = xy_coords[1] + (self.warp_noise[1].noise(xy_coords) * self.warp_size)
     53        return (x_coord, y_coord)
     54
     55    def point(self, canvas: Image, xy_coords: Tuple[int, int]) -> None:
     56        """
     57        Determines colour and draws a point on an image.
     58        """
     59        color = self.determine_shade(xy_coords)
     60        if color is None:
     61            return
     62        xy_coords = self.adjust_point(xy_coords)
     63
     64        if self.in_bounds(canvas, xy_coords):
     65            canvas.putpixel((int(xy_coords[0]), int(xy_coords[1])), color)
     66
     67
     68    def in_bounds(self, canvas: Image, xy_coords: Tuple[int, int]) -> bool:
     69        """
     70        determined whether xy_coords are within the size of canvas image
     71        """
     72        if (xy_coords[0] < 0) or (xy_coords[0] >= canvas.width):
     73            return False
     74        if (xy_coords[1] < 0) or (xy_coords[1] >= canvas.height):
     75            return False
     76        return True
     77
     78
     79    def weighted_point(self, canvas: Image, xy_coords: Tuple[int, int], weight: int):
     80        """
     81        Determines colour and draws a weighted point on an image.
     82        """
     83        color = self.determine_shade(xy_coords)
     84        if self.warp_size != 0:
     85            xy_coords = self.adjust_point(xy_coords)
     86
     87        for x_coord in range(0, weight):
     88            for y_coord in range(0, weight):
     89                new_point = (int(xy_coords[0]+x_coord), int(xy_coords[1]+y_coord))
     90                if self.in_bounds(canvas, new_point):
     91                    canvas.putpixel(new_point, color)
     92
     93
     94    def pixels_inside_edge(self, edge_pixels: List) -> List:
     95        """
     96        Returns a list of  pixels from inside a edge of points using ray casting algorithm
     97        https://en.wikipedia.org/wiki/Point_in_polygon
     98        vertex correction requires improvements, unusual or particularly angular shapes may
     99        cause difficulties
    100        """
    101        inner_pixels = []
    102        x_coords = {i[0] for i in edge_pixels}
    103        for x_coord in range(min(x_coords), max(x_coords)+1):
    104            y_coords = {i[1] for i in edge_pixels if i[0] == x_coord}
    105            y_coords = [i for i in y_coords if i-1 not in y_coords]
    106            ray_count = 0
    107            for y_coord in range(min(y_coords), max(y_coords)+1):
    108                if y_coord in y_coords and (x_coord, y_coord):
    109                    ray_count += 1
    110                if ray_count % 2 == 1:
    111                    inner_pixels.append((x_coord, y_coord))
    112
    113        return list(set(inner_pixels + edge_pixels))
    114
    115
    116    def pixels_between_two_points(self, xy_coord_1: Tuple, xy_coord_2: Tuple) -> List:
    117        """
    118        Returns a list of pixels that form a straight line between two points.
    119
    120        Parameters:
    121        xy_coord_1 (int iterable): Coordinates for first point.
    122        xy_coord_2 (int iterable): Coordinates for second point.
    123
    124        Returns:
    125        pixels (int iterable): List of pixels between the two points.
    126        """
    127        if abs(xy_coord_1[0] - xy_coord_2[0]) > abs(xy_coord_1[1] - xy_coord_2[1]):
    128            if xy_coord_1[0] > xy_coord_2[0]:
    129                x_step = -1
    130            else:
    131                x_step = 1
    132            y_step = (abs(xy_coord_1[1] - xy_coord_2[1]) / abs(xy_coord_1[0] - xy_coord_2[0]))
    133            if xy_coord_1[1] > xy_coord_2[1]:
    134                y_step *= -1
    135            i_stop = abs(xy_coord_1[0] - xy_coord_2[0])
    136        else:
    137            if xy_coord_1[1] > xy_coord_2[1]:
    138                y_step = -1
    139            else:
    140                y_step = 1
    141            x_step = (abs(xy_coord_1[0] - xy_coord_2[0]) / abs(xy_coord_1[1] - xy_coord_2[1]))
    142            if xy_coord_1[0] > xy_coord_2[0]:
    143                x_step *= -1
    144            i_stop = abs(xy_coord_1[1]-xy_coord_2[1])
    145
    146        pixels = []
    147        x_coord, y_coord = xy_coord_1
    148        for _ in range(0, int(i_stop) + 1):
    149            pixels.append((int(x_coord), int(y_coord)))
    150            x_coord += x_step
    151            y_coord += y_step
    152        return pixels
    153
    154
    155    def line(
    156            self,
    157            canvas: Image,
    158            xy_coords_1: Tuple[int, int],
    159            xy_coords_2: Tuple[int, int],
    160            weight: int = 2,
    161        ) -> None:
    162        """
    163        Draws a weighted line on the image.
    164        """
    165        for pixel in self.pixels_between_two_points(xy_coords_1, xy_coords_2):
    166            self.weighted_point(canvas, pixel, weight)
    167
    168
    169    def fill(self, canvas: Image) -> None:
    170        """
    171        Fills the entire image with color.
    172        """
    173        # we'll temporarily turn off warping as it isn't needed here
    174        warp_size_keeper = self.warp_size
    175        self.warp_size = 0
    176        for x_coord in range(0, canvas.width):
    177            for y_coord in range(0, canvas.height):
    178                self.point(canvas, (x_coord, y_coord))
    179        #[[self.point(canvas, (x, y)) for x in range(0, canvas.width)]
    180        # for y in range(0, canvas.height)]
    181        self.warp_size = warp_size_keeper
    182
    183
    184    def get_shape_edge(self, list_of_points: List[Tuple[int, int]]) -> List[Tuple]:
    185        """
    186        Returns list of coordinates making up the edge of a shape
    187        """
    188        edge = self.pixels_between_two_points(
    189            list_of_points[-1], list_of_points[0])
    190        for i in range(0, len(list_of_points)-1):
    191            edge += self.pixels_between_two_points(
    192                list_of_points[i], list_of_points[i+1])
    193        return edge
    194
    195
    196    def shape(self, canvas: Image, points: List[Tuple[int, int]]) -> None:
    197        """
    198        Draws a shape on an image based on a list of points.
    199        """
    200        edge = self.get_shape_edge(points)
    201        for pixel in self.pixels_inside_edge(edge):
    202            self.point(canvas, pixel)
    203
    204
    205    def shape_outline(
    206            self,
    207            canvas: Image,
    208            points: List[Tuple[int, int]],
    209            weight: int = 2,
    210        ) -> None:
    211        """
    212        Draws a shape outline on an image based on a list of points.
    213        """
    214        for pixel in self.get_shape_edge(points):
    215            self.weighted_point(canvas, pixel, weight)
    216
    217
    218    def rectangle(
    219            self,
    220            canvas: Image,
    221            top_corner: Tuple[int, int],
    222            width: int,
    223            height: int,
    224        ) -> None:
    225        """
    226        Draws a rectangle on the image.
    227        """
    228        for x_coord in range(top_corner[0], top_corner[0] + width):
    229            for y_coord in range(top_corner[1], top_corner[1] + height):
    230                self.point(canvas, (x_coord, y_coord))
    231
    232    def square(
    233            self,
    234            canvas: Image,
    235            top_corner: Tuple[int, int],
    236            size: int,
    237        ) -> None:
    238        """
    239        Draws a square on the canvas
    240        """
    241        self.rectangle(canvas, top_corner, size, size)
    242
    243
    244    def triangle(
    245            self,
    246            canvas,
    247            xy1: Tuple[int, int],
    248            xy2: Tuple[int, int],
    249            xy3: Tuple[int, int],
    250        ) -> None:
    251        """
    252        Draws a triangle on the image.
    253        This is the same as calling Shade.shape with a list of three points.
    254        """
    255        self.shape(canvas, [xy1, xy2, xy3])
    256
    257
    258    def triangle_outline(
    259            self,
    260            canvas,
    261            xy1: Tuple[int, int],
    262            xy2: Tuple[int, int],
    263            xy3: Tuple[int, int],
    264            weight: int = 2,
    265        ) -> None:
    266        """
    267        Draws a triangle outline on the image.
    268        Note that this is the same as calling Shade.shape_outline with a list of three points.
    269        """
    270        self.shape_outline(canvas, [xy1, xy2, xy3], weight)
    271
    272
    273    def get_circle_edge(
    274            self,
    275            center: Tuple[int, int],
    276            radius: int,
    277        ) -> List[Tuple[int, int]]:
    278        """
    279        Returns the edge coordinates of a circle
    280        """
    281        edge_pixels = []
    282        circumference = radius * 2 * np.pi
    283        for i in range(0, int(circumference)+1):
    284            angle = (i/circumference) * 360
    285            opposite = np.sin(np.radians(angle)) * radius
    286            adjacent = np.cos(np.radians(angle)) * radius
    287            point = (int(center[0] + adjacent), int(center[1] + opposite))
    288            edge_pixels.append(point)
    289        return edge_pixels
    290
    291
    292    def circle(
    293            self,
    294            canvas: Image,
    295            center: Tuple[int, int],
    296            radius: int,
    297        ) -> None:
    298        """
    299        Draws a circle on the image.
    300        """
    301        edge_pixels = self.get_circle_edge(center, radius)
    302        for pixel in self.pixels_inside_edge(edge_pixels):
    303            self.point(canvas, pixel)
    304
    305
    306    def circle_outline(
    307            self,
    308            canvas: Image,
    309            center: Tuple[int, int],
    310            radius: int,
    311            weight: int = 2,
    312        ) -> None:
    313        """
    314        Draws a circle outline on the image.
    315        """
    316        edge_pixels = self.get_circle_edge(center, radius)
    317        for pixel in edge_pixels:
    318            self.weighted_point(canvas, pixel, weight)
    319
    320
    321    def circle_slice(
    322            self,
    323            canvas: Image,
    324            center: Tuple[int, int],
    325            radius: int,
    326            start_angle: int,
    327            degrees_of_slice: int,
    328        ) -> None:
    329        """
    330        Draws a partial circle based on degrees.
    331        (will have the appearance of a 'pizza slice' or 'pacman' depending on degrees).
    332        """
    333        # due to Shade.pixels_between_two_points vertex correction issues,
    334        # breaks down shape into smaller parts
    335        def _internal(canvas, center, radius, start_angle, degrees_of_slice):
    336            circumference = radius * 2 * np.pi
    337
    338            start_point = int(
    339                (((start_angle - 90) % 361) / 360) * circumference)
    340            slice_length = int((degrees_of_slice / 360) * circumference)
    341            end_point = start_point + slice_length
    342            edge_pixels = []
    343
    344            for i in range(start_point, end_point + 1):
    345                angle = (i/circumference) * 360
    346                opposite = np.sin(np.radians(angle)) * radius
    347                adjacent = np.cos(np.radians(angle)) * radius
    348                point = (int(center[0] + adjacent), int(center[1] + opposite))
    349                edge_pixels.append(point)
    350                if i in [start_point, end_point]:
    351                    edge_pixels += self.pixels_between_two_points(point, center)
    352
    353            for pixel in self.pixels_inside_edge(edge_pixels):
    354                self.point(canvas, pixel)
    355
    356        if degrees_of_slice > 180:
    357            _internal(canvas, center, radius, start_angle, 180)
    358            _internal(canvas, center, radius, start_angle +
    359                      180, degrees_of_slice - 180)
    360        else:
    361            _internal(canvas, center, radius, start_angle, degrees_of_slice)
    362
    363
    364class BlockColor(Shade):
    365    """
    366    Type of shade that will always fill with defined color without variation.
    367    """
    368    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    369        """
    370        Ignores xy coordinates and returns defined color.
    371        """
    372        return self.color
    373
    374
    375class NoiseGradient(Shade):
    376    """
    377    Type of shade that will produce varying gradient based on noise fields.
    378
    379    Unique Parameters:
    380    color_variance: How much noise is allowed to affect the color from the central shade
    381    color_fields: A noise field for each channel (r,g,b)
    382    """
    383
    384    def __init__(
    385            self,
    386            color: Tuple[int, int, int] = (0, 0, 0),
    387            warp_noise: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    388            warp_size: int = 0,
    389            color_variance: int = 70,
    390            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    391        ):
    392        super().__init__(color, warp_noise, warp_size)
    393        self.color_variance = color_variance
    394        self.color_fields = tuple(color_fields)
    395
    396
    397    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    398        """
    399        Measures noise from coordinates and affects color based upon return.
    400        """
    401        def apply_noise(i):
    402            noise = self.color_fields[i].noise(xy_coords) - 0.5
    403            color_affect = noise * (2*self.color_variance)
    404            return self.color[i] + color_affect
    405        return color_clamp([apply_noise(i) for i in range(len(self.color))])
    406
    407
    408class DomainWarpGradient(Shade):
    409    """
    410    Type of shade that will produce varying gradient based on recursive noise fields.
    411
    412    Unique Parameters:
    413    color_variance: How much noise is allowed to affect the color from the central shade
    414    color_fields: A noise field for each channel (r,g,b)
    415    depth: Number of recursions within noise to make
    416    feedback: Affect of recursive calls, recomended around 0-2
    417    """
    418
    419    def __init__(
    420            self,
    421            color: Tuple[int, int, int] = (0, 0, 0),
    422            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    423            warp_size: int = 0,
    424            color_variance: int = 70,
    425            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    426            depth: int = 2,
    427            feedback: float = 0.7,
    428        ):
    429        super().__init__(color, warp_noise, warp_size)
    430        self.color_variance = color_variance
    431        self.color_fields = tuple(color_fields)
    432        self.depth = depth
    433        self.feedback = feedback
    434
    435
    436    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    437        """
    438        Determines shade based on xy coordinates.
    439        """
    440        def apply_noise(i):
    441            noise = self.color_fields[i].recursive_noise(
    442                xy_coords, self.depth, self.feedback) - 0.5
    443            color_affect = noise * (2*self.color_variance)
    444            return self.color[i] + color_affect
    445        return color_clamp([apply_noise(i) for i in range(len(self.color))])
    446
    447
    448class SwirlOfShades(Shade):
    449    """
    450    Type of shade that will select from list of other shades based on recursive noise field.
    451
    452    Unique Parameters:
    453    swirl_field: a NoiseField from which the selection of the shade is made
    454    depth: Number of recursive calls to make from swirl_field.noise (defaults to 0)
    455    feedback: Affect of recursive calls from swirl_field.noise
    456    shades: this one is very specific, and determines when shades are used.
    457            must be list of tuples of this form:
    458            (lower_bound, upper_bound, Shade)
    459
    460    because the 'shades' arguments potentially confusing, here's an example.
    461    The below will color white when noise of 0 - 0.5 is returned, and black if noise of 0.5 - 1
    462    [(0, 0.5, shades.BlockColor((255, 255, 255)), (0.5, 1, shades.BlockColor((0, 0, 0)))]
    463    """
    464    def __init__(
    465            self,
    466            shades: List[Tuple[float, float, Shade]],
    467            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    468            warp_size: int = 0,
    469            color_variance: int = 70,
    470            swirl_field: NoiseField = NoiseField(),
    471            depth: int = 1,
    472            feedback: float = 0.7,
    473        ):
    474        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    475        self.color_variance = color_variance
    476        self.swirl_field = swirl_field
    477        self.depth = depth
    478        self.feedback = feedback
    479        self.shades = shades
    480
    481
    482    def determine_shade(self, xy_coords: Tuple[int, int]):
    483        """
    484        Determines shade based on xy coordinates.
    485        """
    486        noise = self.swirl_field.recursive_noise(xy_coords, self.depth, self.feedback)
    487        shades = [i for i in self.shades if i[0] <= noise < i[1]]
    488        if len(shades) > 0:
    489            shade = shades[0][2]
    490            return shade.determine_shade(xy_coords)
    491        return None
    492
    493
    494class LinearGradient(Shade):
    495    """
    496    Type of shade that will determine color based on transition between various 'color_points'
    497
    498    Unique Parameters:
    499    color_points: Groups of colours and coordinate at which they should appear
    500    axis: 0 for horizontal gradient, 1 for vertical
    501
    502    Here's an example of color_points
    503    in this, anything before 50 (on whichever axis specified) will be black,
    504    anything after 100 will be white
    505    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    506    [((0, 0, 0), 50), ((250, 250, 250), 100)]
    507    """
    508
    509    def __init__(
    510            self,
    511            color_points: List[Tuple[int, Tuple[int, int, int]]],
    512            axis: int = 0,
    513            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    514            warp_size: int = 0,
    515        ):
    516        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    517        self.color_points = color_points
    518        self.axis = axis
    519
    520
    521    def determine_shade(self, xy_coords):
    522        """
    523        Determines shade based on xy coordinates.
    524
    525        Parameters:
    526        xy (iterable): xy coordinates
    527
    528        Returns:
    529        color in form of tuple
    530        """
    531        larger = [i[1] for i in self.color_points if i[1] >= xy_coords[self.axis]]
    532        smaller = [i[1] for i in self.color_points if i[1] < xy_coords[self.axis]]
    533        if len(smaller) == 0:
    534            next_item = min(larger)
    535            next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    536            return next_color
    537        if len(larger) == 0:
    538            last_item = max(smaller)
    539            last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    540            return last_color
    541
    542        next_item = min(larger)
    543        last_item = max(smaller)
    544
    545        next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    546        last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    547        distance_from_next = abs(next_item - xy_coords[self.axis])
    548        distance_from_last = abs(last_item - xy_coords[self.axis])
    549        from_last_to_next = distance_from_last / (distance_from_next + distance_from_last)
    550
    551        color = [0 for i in len(next_color)]
    552        for i, _ in enumerate(next_color):
    553            color_difference = (
    554                last_color[i] - next_color[i]) * from_last_to_next
    555            color[i] = last_color[i] - color_difference
    556
    557        return color_clamp(color)
    558
    559
    560class VerticalGradient(LinearGradient):
    561    """
    562    Type of shade that will determine color based on transition between various 'color_points'
    563
    564    Unique Parameters:
    565    color_points: Groups of colours and coordinate at which they should appear
    566
    567    Here's an example of color_points
    568    in this, anything before 50 (on y axis) will be black,
    569    anything after 100 will be white
    570    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    571    """
    572    def __init__(
    573        self,
    574        color_points: List[Tuple[int, Tuple[int, int, int]]],
    575        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    576        warp_size: int = 0,
    577    ):
    578        super().__init__(
    579            color_points=color_points,
    580            axis=1,
    581            warp_noise=warp_noise,
    582            warp_size=warp_size,
    583        )
    584
    585
    586class HorizontalGradient(LinearGradient):
    587    """
    588    Type of shade that will determine color based on transition between various 'color_points'
    589
    590    Unique Parameters:
    591    color_points: Groups of colours and coordinate at which they should appear
    592
    593    Here's an example of color_points
    594    in this, anything before 50 (on x axis) will be black,
    595    anything after 100 will be white
    596    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    597    """
    598
    599    def __init__(self,
    600        color_points: List[Tuple[int, Tuple[int, int, int]]],
    601        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    602        warp_size: int = 0,
    603    ):
    604        super().__init__(
    605            color_points=color_points,
    606            axis=0,
    607            warp_noise=warp_noise,
    608            warp_size=warp_size,
    609        )

class Shade(abc.ABC): View Source

     18class Shade(ABC):
     19    """
     20    An Abstract base clase Shade.
     21    Methods are used to mark shapes onto images according to various color rules.
     22
     23    Initialisation parameters of warp_noise takes two noise_fields affecting how
     24    much a point is moved across x and y axis.
     25
     26    warp_size determines the amount that a warp_noise result of 1 (maximum perlin
     27    value) translates as
     28    """
     29
     30    def __init__(
     31            self,
     32            color: Tuple[int, int, int] = (0, 0, 0),
     33            warp_noise: Tuple[NoiseField] = noise_fields(channels=2),
     34            warp_size: float = 0,
     35        ):
     36        self.color = color
     37        self.warp_noise = warp_noise
     38        self.warp_size = warp_size
     39
     40    @abstractmethod
     41    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
     42        """
     43        Determines the shade/color for given xy coordinate.
     44        """
     45
     46    def adjust_point(self, xy_coords: Tuple[int, int]) -> Tuple[int, int]:
     47        """
     48        If warp is applied in shade, appropriately adjusts location of point.
     49        """
     50        if self.warp_size == 0:
     51            return xy_coords
     52        x_coord = xy_coords[0] + (self.warp_noise[0].noise(xy_coords) * self.warp_size)
     53        y_coord = xy_coords[1] + (self.warp_noise[1].noise(xy_coords) * self.warp_size)
     54        return (x_coord, y_coord)
     55
     56    def point(self, canvas: Image, xy_coords: Tuple[int, int]) -> None:
     57        """
     58        Determines colour and draws a point on an image.
     59        """
     60        color = self.determine_shade(xy_coords)
     61        if color is None:
     62            return
     63        xy_coords = self.adjust_point(xy_coords)
     64
     65        if self.in_bounds(canvas, xy_coords):
     66            canvas.putpixel((int(xy_coords[0]), int(xy_coords[1])), color)
     67
     68
     69    def in_bounds(self, canvas: Image, xy_coords: Tuple[int, int]) -> bool:
     70        """
     71        determined whether xy_coords are within the size of canvas image
     72        """
     73        if (xy_coords[0] < 0) or (xy_coords[0] >= canvas.width):
     74            return False
     75        if (xy_coords[1] < 0) or (xy_coords[1] >= canvas.height):
     76            return False
     77        return True
     78
     79
     80    def weighted_point(self, canvas: Image, xy_coords: Tuple[int, int], weight: int):
     81        """
     82        Determines colour and draws a weighted point on an image.
     83        """
     84        color = self.determine_shade(xy_coords)
     85        if self.warp_size != 0:
     86            xy_coords = self.adjust_point(xy_coords)
     87
     88        for x_coord in range(0, weight):
     89            for y_coord in range(0, weight):
     90                new_point = (int(xy_coords[0]+x_coord), int(xy_coords[1]+y_coord))
     91                if self.in_bounds(canvas, new_point):
     92                    canvas.putpixel(new_point, color)
     93
     94
     95    def pixels_inside_edge(self, edge_pixels: List) -> List:
     96        """
     97        Returns a list of  pixels from inside a edge of points using ray casting algorithm
     98        https://en.wikipedia.org/wiki/Point_in_polygon
     99        vertex correction requires improvements, unusual or particularly angular shapes may
    100        cause difficulties
    101        """
    102        inner_pixels = []
    103        x_coords = {i[0] for i in edge_pixels}
    104        for x_coord in range(min(x_coords), max(x_coords)+1):
    105            y_coords = {i[1] for i in edge_pixels if i[0] == x_coord}
    106            y_coords = [i for i in y_coords if i-1 not in y_coords]
    107            ray_count = 0
    108            for y_coord in range(min(y_coords), max(y_coords)+1):
    109                if y_coord in y_coords and (x_coord, y_coord):
    110                    ray_count += 1
    111                if ray_count % 2 == 1:
    112                    inner_pixels.append((x_coord, y_coord))
    113
    114        return list(set(inner_pixels + edge_pixels))
    115
    116
    117    def pixels_between_two_points(self, xy_coord_1: Tuple, xy_coord_2: Tuple) -> List:
    118        """
    119        Returns a list of pixels that form a straight line between two points.
    120
    121        Parameters:
    122        xy_coord_1 (int iterable): Coordinates for first point.
    123        xy_coord_2 (int iterable): Coordinates for second point.
    124
    125        Returns:
    126        pixels (int iterable): List of pixels between the two points.
    127        """
    128        if abs(xy_coord_1[0] - xy_coord_2[0]) > abs(xy_coord_1[1] - xy_coord_2[1]):
    129            if xy_coord_1[0] > xy_coord_2[0]:
    130                x_step = -1
    131            else:
    132                x_step = 1
    133            y_step = (abs(xy_coord_1[1] - xy_coord_2[1]) / abs(xy_coord_1[0] - xy_coord_2[0]))
    134            if xy_coord_1[1] > xy_coord_2[1]:
    135                y_step *= -1
    136            i_stop = abs(xy_coord_1[0] - xy_coord_2[0])
    137        else:
    138            if xy_coord_1[1] > xy_coord_2[1]:
    139                y_step = -1
    140            else:
    141                y_step = 1
    142            x_step = (abs(xy_coord_1[0] - xy_coord_2[0]) / abs(xy_coord_1[1] - xy_coord_2[1]))
    143            if xy_coord_1[0] > xy_coord_2[0]:
    144                x_step *= -1
    145            i_stop = abs(xy_coord_1[1]-xy_coord_2[1])
    146
    147        pixels = []
    148        x_coord, y_coord = xy_coord_1
    149        for _ in range(0, int(i_stop) + 1):
    150            pixels.append((int(x_coord), int(y_coord)))
    151            x_coord += x_step
    152            y_coord += y_step
    153        return pixels
    154
    155
    156    def line(
    157            self,
    158            canvas: Image,
    159            xy_coords_1: Tuple[int, int],
    160            xy_coords_2: Tuple[int, int],
    161            weight: int = 2,
    162        ) -> None:
    163        """
    164        Draws a weighted line on the image.
    165        """
    166        for pixel in self.pixels_between_two_points(xy_coords_1, xy_coords_2):
    167            self.weighted_point(canvas, pixel, weight)
    168
    169
    170    def fill(self, canvas: Image) -> None:
    171        """
    172        Fills the entire image with color.
    173        """
    174        # we'll temporarily turn off warping as it isn't needed here
    175        warp_size_keeper = self.warp_size
    176        self.warp_size = 0
    177        for x_coord in range(0, canvas.width):
    178            for y_coord in range(0, canvas.height):
    179                self.point(canvas, (x_coord, y_coord))
    180        #[[self.point(canvas, (x, y)) for x in range(0, canvas.width)]
    181        # for y in range(0, canvas.height)]
    182        self.warp_size = warp_size_keeper
    183
    184
    185    def get_shape_edge(self, list_of_points: List[Tuple[int, int]]) -> List[Tuple]:
    186        """
    187        Returns list of coordinates making up the edge of a shape
    188        """
    189        edge = self.pixels_between_two_points(
    190            list_of_points[-1], list_of_points[0])
    191        for i in range(0, len(list_of_points)-1):
    192            edge += self.pixels_between_two_points(
    193                list_of_points[i], list_of_points[i+1])
    194        return edge
    195
    196
    197    def shape(self, canvas: Image, points: List[Tuple[int, int]]) -> None:
    198        """
    199        Draws a shape on an image based on a list of points.
    200        """
    201        edge = self.get_shape_edge(points)
    202        for pixel in self.pixels_inside_edge(edge):
    203            self.point(canvas, pixel)
    204
    205
    206    def shape_outline(
    207            self,
    208            canvas: Image,
    209            points: List[Tuple[int, int]],
    210            weight: int = 2,
    211        ) -> None:
    212        """
    213        Draws a shape outline on an image based on a list of points.
    214        """
    215        for pixel in self.get_shape_edge(points):
    216            self.weighted_point(canvas, pixel, weight)
    217
    218
    219    def rectangle(
    220            self,
    221            canvas: Image,
    222            top_corner: Tuple[int, int],
    223            width: int,
    224            height: int,
    225        ) -> None:
    226        """
    227        Draws a rectangle on the image.
    228        """
    229        for x_coord in range(top_corner[0], top_corner[0] + width):
    230            for y_coord in range(top_corner[1], top_corner[1] + height):
    231                self.point(canvas, (x_coord, y_coord))
    232
    233    def square(
    234            self,
    235            canvas: Image,
    236            top_corner: Tuple[int, int],
    237            size: int,
    238        ) -> None:
    239        """
    240        Draws a square on the canvas
    241        """
    242        self.rectangle(canvas, top_corner, size, size)
    243
    244
    245    def triangle(
    246            self,
    247            canvas,
    248            xy1: Tuple[int, int],
    249            xy2: Tuple[int, int],
    250            xy3: Tuple[int, int],
    251        ) -> None:
    252        """
    253        Draws a triangle on the image.
    254        This is the same as calling Shade.shape with a list of three points.
    255        """
    256        self.shape(canvas, [xy1, xy2, xy3])
    257
    258
    259    def triangle_outline(
    260            self,
    261            canvas,
    262            xy1: Tuple[int, int],
    263            xy2: Tuple[int, int],
    264            xy3: Tuple[int, int],
    265            weight: int = 2,
    266        ) -> None:
    267        """
    268        Draws a triangle outline on the image.
    269        Note that this is the same as calling Shade.shape_outline with a list of three points.
    270        """
    271        self.shape_outline(canvas, [xy1, xy2, xy3], weight)
    272
    273
    274    def get_circle_edge(
    275            self,
    276            center: Tuple[int, int],
    277            radius: int,
    278        ) -> List[Tuple[int, int]]:
    279        """
    280        Returns the edge coordinates of a circle
    281        """
    282        edge_pixels = []
    283        circumference = radius * 2 * np.pi
    284        for i in range(0, int(circumference)+1):
    285            angle = (i/circumference) * 360
    286            opposite = np.sin(np.radians(angle)) * radius
    287            adjacent = np.cos(np.radians(angle)) * radius
    288            point = (int(center[0] + adjacent), int(center[1] + opposite))
    289            edge_pixels.append(point)
    290        return edge_pixels
    291
    292
    293    def circle(
    294            self,
    295            canvas: Image,
    296            center: Tuple[int, int],
    297            radius: int,
    298        ) -> None:
    299        """
    300        Draws a circle on the image.
    301        """
    302        edge_pixels = self.get_circle_edge(center, radius)
    303        for pixel in self.pixels_inside_edge(edge_pixels):
    304            self.point(canvas, pixel)
    305
    306
    307    def circle_outline(
    308            self,
    309            canvas: Image,
    310            center: Tuple[int, int],
    311            radius: int,
    312            weight: int = 2,
    313        ) -> None:
    314        """
    315        Draws a circle outline on the image.
    316        """
    317        edge_pixels = self.get_circle_edge(center, radius)
    318        for pixel in edge_pixels:
    319            self.weighted_point(canvas, pixel, weight)
    320
    321
    322    def circle_slice(
    323            self,
    324            canvas: Image,
    325            center: Tuple[int, int],
    326            radius: int,
    327            start_angle: int,
    328            degrees_of_slice: int,
    329        ) -> None:
    330        """
    331        Draws a partial circle based on degrees.
    332        (will have the appearance of a 'pizza slice' or 'pacman' depending on degrees).
    333        """
    334        # due to Shade.pixels_between_two_points vertex correction issues,
    335        # breaks down shape into smaller parts
    336        def _internal(canvas, center, radius, start_angle, degrees_of_slice):
    337            circumference = radius * 2 * np.pi
    338
    339            start_point = int(
    340                (((start_angle - 90) % 361) / 360) * circumference)
    341            slice_length = int((degrees_of_slice / 360) * circumference)
    342            end_point = start_point + slice_length
    343            edge_pixels = []
    344
    345            for i in range(start_point, end_point + 1):
    346                angle = (i/circumference) * 360
    347                opposite = np.sin(np.radians(angle)) * radius
    348                adjacent = np.cos(np.radians(angle)) * radius
    349                point = (int(center[0] + adjacent), int(center[1] + opposite))
    350                edge_pixels.append(point)
    351                if i in [start_point, end_point]:
    352                    edge_pixels += self.pixels_between_two_points(point, center)
    353
    354            for pixel in self.pixels_inside_edge(edge_pixels):
    355                self.point(canvas, pixel)
    356
    357        if degrees_of_slice > 180:
    358            _internal(canvas, center, radius, start_angle, 180)
    359            _internal(canvas, center, radius, start_angle +
    360                      180, degrees_of_slice - 180)
    361        else:
    362            _internal(canvas, center, radius, start_angle, degrees_of_slice)

An Abstract base clase Shade. Methods are used to mark shapes onto images according to various color rules.

Initialisation parameters of warp_noise takes two noise_fields affecting how much a point is moved across x and y axis.

warp_size determines the amount that a warp_noise result of 1 (maximum perlin value) translates as

color
warp_noise
warp_size
@abstractmethod
def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]: View Source

    40    @abstractmethod
    41    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    42        """
    43        Determines the shade/color for given xy coordinate.
    44        """

Determines the shade/color for given xy coordinate.

def adjust_point(self, xy_coords: Tuple[int, int]) -> Tuple[int, int]: View Source

    46    def adjust_point(self, xy_coords: Tuple[int, int]) -> Tuple[int, int]:
    47        """
    48        If warp is applied in shade, appropriately adjusts location of point.
    49        """
    50        if self.warp_size == 0:
    51            return xy_coords
    52        x_coord = xy_coords[0] + (self.warp_noise[0].noise(xy_coords) * self.warp_size)
    53        y_coord = xy_coords[1] + (self.warp_noise[1].noise(xy_coords) * self.warp_size)
    54        return (x_coord, y_coord)

If warp is applied in shade, appropriately adjusts location of point.

def point( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, xy_coords: Tuple[int, int]) -> None: View Source

    56    def point(self, canvas: Image, xy_coords: Tuple[int, int]) -> None:
    57        """
    58        Determines colour and draws a point on an image.
    59        """
    60        color = self.determine_shade(xy_coords)
    61        if color is None:
    62            return
    63        xy_coords = self.adjust_point(xy_coords)
    64
    65        if self.in_bounds(canvas, xy_coords):
    66            canvas.putpixel((int(xy_coords[0]), int(xy_coords[1])), color)

Determines colour and draws a point on an image.

def in_bounds( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, xy_coords: Tuple[int, int]) -> bool: View Source

    69    def in_bounds(self, canvas: Image, xy_coords: Tuple[int, int]) -> bool:
    70        """
    71        determined whether xy_coords are within the size of canvas image
    72        """
    73        if (xy_coords[0] < 0) or (xy_coords[0] >= canvas.width):
    74            return False
    75        if (xy_coords[1] < 0) or (xy_coords[1] >= canvas.height):
    76            return False
    77        return True

determined whether xy_coords are within the size of canvas image

def weighted_point( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, xy_coords: Tuple[int, int], weight: int): View Source

    80    def weighted_point(self, canvas: Image, xy_coords: Tuple[int, int], weight: int):
    81        """
    82        Determines colour and draws a weighted point on an image.
    83        """
    84        color = self.determine_shade(xy_coords)
    85        if self.warp_size != 0:
    86            xy_coords = self.adjust_point(xy_coords)
    87
    88        for x_coord in range(0, weight):
    89            for y_coord in range(0, weight):
    90                new_point = (int(xy_coords[0]+x_coord), int(xy_coords[1]+y_coord))
    91                if self.in_bounds(canvas, new_point):
    92                    canvas.putpixel(new_point, color)

Determines colour and draws a weighted point on an image.

def pixels_inside_edge(self, edge_pixels: List) -> List: View Source

     95    def pixels_inside_edge(self, edge_pixels: List) -> List:
     96        """
     97        Returns a list of  pixels from inside a edge of points using ray casting algorithm
     98        https://en.wikipedia.org/wiki/Point_in_polygon
     99        vertex correction requires improvements, unusual or particularly angular shapes may
    100        cause difficulties
    101        """
    102        inner_pixels = []
    103        x_coords = {i[0] for i in edge_pixels}
    104        for x_coord in range(min(x_coords), max(x_coords)+1):
    105            y_coords = {i[1] for i in edge_pixels if i[0] == x_coord}
    106            y_coords = [i for i in y_coords if i-1 not in y_coords]
    107            ray_count = 0
    108            for y_coord in range(min(y_coords), max(y_coords)+1):
    109                if y_coord in y_coords and (x_coord, y_coord):
    110                    ray_count += 1
    111                if ray_count % 2 == 1:
    112                    inner_pixels.append((x_coord, y_coord))
    113
    114        return list(set(inner_pixels + edge_pixels))

Returns a list of pixels from inside a edge of points using ray casting algorithm https://en.wikipedia.org/wiki/Point_in_polygon vertex correction requires improvements, unusual or particularly angular shapes may cause difficulties

def pixels_between_two_points(self, xy_coord_1: Tuple, xy_coord_2: Tuple) -> List: View Source

    117    def pixels_between_two_points(self, xy_coord_1: Tuple, xy_coord_2: Tuple) -> List:
    118        """
    119        Returns a list of pixels that form a straight line between two points.
    120
    121        Parameters:
    122        xy_coord_1 (int iterable): Coordinates for first point.
    123        xy_coord_2 (int iterable): Coordinates for second point.
    124
    125        Returns:
    126        pixels (int iterable): List of pixels between the two points.
    127        """
    128        if abs(xy_coord_1[0] - xy_coord_2[0]) > abs(xy_coord_1[1] - xy_coord_2[1]):
    129            if xy_coord_1[0] > xy_coord_2[0]:
    130                x_step = -1
    131            else:
    132                x_step = 1
    133            y_step = (abs(xy_coord_1[1] - xy_coord_2[1]) / abs(xy_coord_1[0] - xy_coord_2[0]))
    134            if xy_coord_1[1] > xy_coord_2[1]:
    135                y_step *= -1
    136            i_stop = abs(xy_coord_1[0] - xy_coord_2[0])
    137        else:
    138            if xy_coord_1[1] > xy_coord_2[1]:
    139                y_step = -1
    140            else:
    141                y_step = 1
    142            x_step = (abs(xy_coord_1[0] - xy_coord_2[0]) / abs(xy_coord_1[1] - xy_coord_2[1]))
    143            if xy_coord_1[0] > xy_coord_2[0]:
    144                x_step *= -1
    145            i_stop = abs(xy_coord_1[1]-xy_coord_2[1])
    146
    147        pixels = []
    148        x_coord, y_coord = xy_coord_1
    149        for _ in range(0, int(i_stop) + 1):
    150            pixels.append((int(x_coord), int(y_coord)))
    151            x_coord += x_step
    152            y_coord += y_step
    153        return pixels

Returns a list of pixels that form a straight line between two points.

Parameters: xy_coord_1 (int iterable): Coordinates for first point. xy_coord_2 (int iterable): Coordinates for second point.

Returns: pixels (int iterable): List of pixels between the two points.

def line( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, xy_coords_1: Tuple[int, int], xy_coords_2: Tuple[int, int], weight: int = 2) -> None: View Source

    156    def line(
    157            self,
    158            canvas: Image,
    159            xy_coords_1: Tuple[int, int],
    160            xy_coords_2: Tuple[int, int],
    161            weight: int = 2,
    162        ) -> None:
    163        """
    164        Draws a weighted line on the image.
    165        """
    166        for pixel in self.pixels_between_two_points(xy_coords_1, xy_coords_2):
    167            self.weighted_point(canvas, pixel, weight)

Draws a weighted line on the image.

def fill( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>) -> None: View Source

    170    def fill(self, canvas: Image) -> None:
    171        """
    172        Fills the entire image with color.
    173        """
    174        # we'll temporarily turn off warping as it isn't needed here
    175        warp_size_keeper = self.warp_size
    176        self.warp_size = 0
    177        for x_coord in range(0, canvas.width):
    178            for y_coord in range(0, canvas.height):
    179                self.point(canvas, (x_coord, y_coord))
    180        #[[self.point(canvas, (x, y)) for x in range(0, canvas.width)]
    181        # for y in range(0, canvas.height)]
    182        self.warp_size = warp_size_keeper

Fills the entire image with color.

def get_shape_edge(self, list_of_points: List[Tuple[int, int]]) -> List[Tuple]: View Source

    185    def get_shape_edge(self, list_of_points: List[Tuple[int, int]]) -> List[Tuple]:
    186        """
    187        Returns list of coordinates making up the edge of a shape
    188        """
    189        edge = self.pixels_between_two_points(
    190            list_of_points[-1], list_of_points[0])
    191        for i in range(0, len(list_of_points)-1):
    192            edge += self.pixels_between_two_points(
    193                list_of_points[i], list_of_points[i+1])
    194        return edge

Returns list of coordinates making up the edge of a shape

def shape( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, points: List[Tuple[int, int]]) -> None: View Source

    197    def shape(self, canvas: Image, points: List[Tuple[int, int]]) -> None:
    198        """
    199        Draws a shape on an image based on a list of points.
    200        """
    201        edge = self.get_shape_edge(points)
    202        for pixel in self.pixels_inside_edge(edge):
    203            self.point(canvas, pixel)

Draws a shape on an image based on a list of points.

def shape_outline( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, points: List[Tuple[int, int]], weight: int = 2) -> None: View Source

    206    def shape_outline(
    207            self,
    208            canvas: Image,
    209            points: List[Tuple[int, int]],
    210            weight: int = 2,
    211        ) -> None:
    212        """
    213        Draws a shape outline on an image based on a list of points.
    214        """
    215        for pixel in self.get_shape_edge(points):
    216            self.weighted_point(canvas, pixel, weight)

Draws a shape outline on an image based on a list of points.

def rectangle( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, top_corner: Tuple[int, int], width: int, height: int) -> None: View Source

    219    def rectangle(
    220            self,
    221            canvas: Image,
    222            top_corner: Tuple[int, int],
    223            width: int,
    224            height: int,
    225        ) -> None:
    226        """
    227        Draws a rectangle on the image.
    228        """
    229        for x_coord in range(top_corner[0], top_corner[0] + width):
    230            for y_coord in range(top_corner[1], top_corner[1] + height):
    231                self.point(canvas, (x_coord, y_coord))

Draws a rectangle on the image.

def square( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, top_corner: Tuple[int, int], size: int) -> None: View Source

    233    def square(
    234            self,
    235            canvas: Image,
    236            top_corner: Tuple[int, int],
    237            size: int,
    238        ) -> None:
    239        """
    240        Draws a square on the canvas
    241        """
    242        self.rectangle(canvas, top_corner, size, size)

Draws a square on the canvas

def triangle( self, canvas, xy1: Tuple[int, int], xy2: Tuple[int, int], xy3: Tuple[int, int]) -> None: View Source

    245    def triangle(
    246            self,
    247            canvas,
    248            xy1: Tuple[int, int],
    249            xy2: Tuple[int, int],
    250            xy3: Tuple[int, int],
    251        ) -> None:
    252        """
    253        Draws a triangle on the image.
    254        This is the same as calling Shade.shape with a list of three points.
    255        """
    256        self.shape(canvas, [xy1, xy2, xy3])

Draws a triangle on the image. This is the same as calling Shade.shape with a list of three points.

def triangle_outline( self, canvas, xy1: Tuple[int, int], xy2: Tuple[int, int], xy3: Tuple[int, int], weight: int = 2) -> None: View Source

    259    def triangle_outline(
    260            self,
    261            canvas,
    262            xy1: Tuple[int, int],
    263            xy2: Tuple[int, int],
    264            xy3: Tuple[int, int],
    265            weight: int = 2,
    266        ) -> None:
    267        """
    268        Draws a triangle outline on the image.
    269        Note that this is the same as calling Shade.shape_outline with a list of three points.
    270        """
    271        self.shape_outline(canvas, [xy1, xy2, xy3], weight)

Draws a triangle outline on the image. Note that this is the same as calling Shade.shape_outline with a list of three points.

def get_circle_edge(self, center: Tuple[int, int], radius: int) -> List[Tuple[int, int]]: View Source

    274    def get_circle_edge(
    275            self,
    276            center: Tuple[int, int],
    277            radius: int,
    278        ) -> List[Tuple[int, int]]:
    279        """
    280        Returns the edge coordinates of a circle
    281        """
    282        edge_pixels = []
    283        circumference = radius * 2 * np.pi
    284        for i in range(0, int(circumference)+1):
    285            angle = (i/circumference) * 360
    286            opposite = np.sin(np.radians(angle)) * radius
    287            adjacent = np.cos(np.radians(angle)) * radius
    288            point = (int(center[0] + adjacent), int(center[1] + opposite))
    289            edge_pixels.append(point)
    290        return edge_pixels

Returns the edge coordinates of a circle

def circle( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, center: Tuple[int, int], radius: int) -> None: View Source

    293    def circle(
    294            self,
    295            canvas: Image,
    296            center: Tuple[int, int],
    297            radius: int,
    298        ) -> None:
    299        """
    300        Draws a circle on the image.
    301        """
    302        edge_pixels = self.get_circle_edge(center, radius)
    303        for pixel in self.pixels_inside_edge(edge_pixels):
    304            self.point(canvas, pixel)

Draws a circle on the image.

def circle_outline( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, center: Tuple[int, int], radius: int, weight: int = 2) -> None: View Source

    307    def circle_outline(
    308            self,
    309            canvas: Image,
    310            center: Tuple[int, int],
    311            radius: int,
    312            weight: int = 2,
    313        ) -> None:
    314        """
    315        Draws a circle outline on the image.
    316        """
    317        edge_pixels = self.get_circle_edge(center, radius)
    318        for pixel in edge_pixels:
    319            self.weighted_point(canvas, pixel, weight)

Draws a circle outline on the image.

def circle_slice( self, canvas: <module 'PIL.Image' from '/usr/lib/python3/dist-packages/PIL/Image.py'>, center: Tuple[int, int], radius: int, start_angle: int, degrees_of_slice: int) -> None: View Source

    322    def circle_slice(
    323            self,
    324            canvas: Image,
    325            center: Tuple[int, int],
    326            radius: int,
    327            start_angle: int,
    328            degrees_of_slice: int,
    329        ) -> None:
    330        """
    331        Draws a partial circle based on degrees.
    332        (will have the appearance of a 'pizza slice' or 'pacman' depending on degrees).
    333        """
    334        # due to Shade.pixels_between_two_points vertex correction issues,
    335        # breaks down shape into smaller parts
    336        def _internal(canvas, center, radius, start_angle, degrees_of_slice):
    337            circumference = radius * 2 * np.pi
    338
    339            start_point = int(
    340                (((start_angle - 90) % 361) / 360) * circumference)
    341            slice_length = int((degrees_of_slice / 360) * circumference)
    342            end_point = start_point + slice_length
    343            edge_pixels = []
    344
    345            for i in range(start_point, end_point + 1):
    346                angle = (i/circumference) * 360
    347                opposite = np.sin(np.radians(angle)) * radius
    348                adjacent = np.cos(np.radians(angle)) * radius
    349                point = (int(center[0] + adjacent), int(center[1] + opposite))
    350                edge_pixels.append(point)
    351                if i in [start_point, end_point]:
    352                    edge_pixels += self.pixels_between_two_points(point, center)
    353
    354            for pixel in self.pixels_inside_edge(edge_pixels):
    355                self.point(canvas, pixel)
    356
    357        if degrees_of_slice > 180:
    358            _internal(canvas, center, radius, start_angle, 180)
    359            _internal(canvas, center, radius, start_angle +
    360                      180, degrees_of_slice - 180)
    361        else:
    362            _internal(canvas, center, radius, start_angle, degrees_of_slice)

Draws a partial circle based on degrees. (will have the appearance of a 'pizza slice' or 'pacman' depending on degrees).

class BlockColor(Shade): View Source

    365class BlockColor(Shade):
    366    """
    367    Type of shade that will always fill with defined color without variation.
    368    """
    369    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    370        """
    371        Ignores xy coordinates and returns defined color.
    372        """
    373        return self.color

Type of shade that will always fill with defined color without variation.

def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]: View Source

    369    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    370        """
    371        Ignores xy coordinates and returns defined color.
    372        """
    373        return self.color

Ignores xy coordinates and returns defined color.

##### Inherited Members

Shade
  Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class NoiseGradient(Shade): View Source

    376class NoiseGradient(Shade):
    377    """
    378    Type of shade that will produce varying gradient based on noise fields.
    379
    380    Unique Parameters:
    381    color_variance: How much noise is allowed to affect the color from the central shade
    382    color_fields: A noise field for each channel (r,g,b)
    383    """
    384
    385    def __init__(
    386            self,
    387            color: Tuple[int, int, int] = (0, 0, 0),
    388            warp_noise: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    389            warp_size: int = 0,
    390            color_variance: int = 70,
    391            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    392        ):
    393        super().__init__(color, warp_noise, warp_size)
    394        self.color_variance = color_variance
    395        self.color_fields = tuple(color_fields)
    396
    397
    398    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    399        """
    400        Measures noise from coordinates and affects color based upon return.
    401        """
    402        def apply_noise(i):
    403            noise = self.color_fields[i].noise(xy_coords) - 0.5
    404            color_affect = noise * (2*self.color_variance)
    405            return self.color[i] + color_affect
    406        return color_clamp([apply_noise(i) for i in range(len(self.color))])

Type of shade that will produce varying gradient based on noise fields.

Unique Parameters: color_variance: How much noise is allowed to affect the color from the central shade color_fields: A noise field for each channel (r,g,b)

NoiseGradient( color: Tuple[int, int, int] = (0, 0, 0), warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0, color_variance: int = 70, color_fields: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>]) View Source

    385    def __init__(
    386            self,
    387            color: Tuple[int, int, int] = (0, 0, 0),
    388            warp_noise: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    389            warp_size: int = 0,
    390            color_variance: int = 70,
    391            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    392        ):
    393        super().__init__(color, warp_noise, warp_size)
    394        self.color_variance = color_variance
    395        self.color_fields = tuple(color_fields)

color_variance
color_fields
def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]: View Source

    398    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    399        """
    400        Measures noise from coordinates and affects color based upon return.
    401        """
    402        def apply_noise(i):
    403            noise = self.color_fields[i].noise(xy_coords) - 0.5
    404            color_affect = noise * (2*self.color_variance)
    405            return self.color[i] + color_affect
    406        return color_clamp([apply_noise(i) for i in range(len(self.color))])

Measures noise from coordinates and affects color based upon return.

##### Inherited Members

Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class DomainWarpGradient(Shade): View Source

    409class DomainWarpGradient(Shade):
    410    """
    411    Type of shade that will produce varying gradient based on recursive noise fields.
    412
    413    Unique Parameters:
    414    color_variance: How much noise is allowed to affect the color from the central shade
    415    color_fields: A noise field for each channel (r,g,b)
    416    depth: Number of recursions within noise to make
    417    feedback: Affect of recursive calls, recomended around 0-2
    418    """
    419
    420    def __init__(
    421            self,
    422            color: Tuple[int, int, int] = (0, 0, 0),
    423            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    424            warp_size: int = 0,
    425            color_variance: int = 70,
    426            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    427            depth: int = 2,
    428            feedback: float = 0.7,
    429        ):
    430        super().__init__(color, warp_noise, warp_size)
    431        self.color_variance = color_variance
    432        self.color_fields = tuple(color_fields)
    433        self.depth = depth
    434        self.feedback = feedback
    435
    436
    437    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    438        """
    439        Determines shade based on xy coordinates.
    440        """
    441        def apply_noise(i):
    442            noise = self.color_fields[i].recursive_noise(
    443                xy_coords, self.depth, self.feedback) - 0.5
    444            color_affect = noise * (2*self.color_variance)
    445            return self.color[i] + color_affect
    446        return color_clamp([apply_noise(i) for i in range(len(self.color))])

Type of shade that will produce varying gradient based on recursive noise fields.

Unique Parameters: color_variance: How much noise is allowed to affect the color from the central shade color_fields: A noise field for each channel (r,g,b) depth: Number of recursions within noise to make feedback: Affect of recursive calls, recomended around 0-2

DomainWarpGradient( color: Tuple[int, int, int] = (0, 0, 0), warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0, color_variance: int = 70, color_fields: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], depth: int = 2, feedback: float = 0.7) View Source

    420    def __init__(
    421            self,
    422            color: Tuple[int, int, int] = (0, 0, 0),
    423            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    424            warp_size: int = 0,
    425            color_variance: int = 70,
    426            color_fields: Tuple[NoiseField, NoiseField, NoiseField] = noise_fields(channels=3),
    427            depth: int = 2,
    428            feedback: float = 0.7,
    429        ):
    430        super().__init__(color, warp_noise, warp_size)
    431        self.color_variance = color_variance
    432        self.color_fields = tuple(color_fields)
    433        self.depth = depth
    434        self.feedback = feedback

color_variance
color_fields
depth
feedback
def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]: View Source

    437    def determine_shade(self, xy_coords: Tuple[int, int]) -> Tuple[int, int, int]:
    438        """
    439        Determines shade based on xy coordinates.
    440        """
    441        def apply_noise(i):
    442            noise = self.color_fields[i].recursive_noise(
    443                xy_coords, self.depth, self.feedback) - 0.5
    444            color_affect = noise * (2*self.color_variance)
    445            return self.color[i] + color_affect
    446        return color_clamp([apply_noise(i) for i in range(len(self.color))])

Determines shade based on xy coordinates.

##### Inherited Members

Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class SwirlOfShades(Shade): View Source

    449class SwirlOfShades(Shade):
    450    """
    451    Type of shade that will select from list of other shades based on recursive noise field.
    452
    453    Unique Parameters:
    454    swirl_field: a NoiseField from which the selection of the shade is made
    455    depth: Number of recursive calls to make from swirl_field.noise (defaults to 0)
    456    feedback: Affect of recursive calls from swirl_field.noise
    457    shades: this one is very specific, and determines when shades are used.
    458            must be list of tuples of this form:
    459            (lower_bound, upper_bound, Shade)
    460
    461    because the 'shades' arguments potentially confusing, here's an example.
    462    The below will color white when noise of 0 - 0.5 is returned, and black if noise of 0.5 - 1
    463    [(0, 0.5, shades.BlockColor((255, 255, 255)), (0.5, 1, shades.BlockColor((0, 0, 0)))]
    464    """
    465    def __init__(
    466            self,
    467            shades: List[Tuple[float, float, Shade]],
    468            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    469            warp_size: int = 0,
    470            color_variance: int = 70,
    471            swirl_field: NoiseField = NoiseField(),
    472            depth: int = 1,
    473            feedback: float = 0.7,
    474        ):
    475        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    476        self.color_variance = color_variance
    477        self.swirl_field = swirl_field
    478        self.depth = depth
    479        self.feedback = feedback
    480        self.shades = shades
    481
    482
    483    def determine_shade(self, xy_coords: Tuple[int, int]):
    484        """
    485        Determines shade based on xy coordinates.
    486        """
    487        noise = self.swirl_field.recursive_noise(xy_coords, self.depth, self.feedback)
    488        shades = [i for i in self.shades if i[0] <= noise < i[1]]
    489        if len(shades) > 0:
    490            shade = shades[0][2]
    491            return shade.determine_shade(xy_coords)
    492        return None

Type of shade that will select from list of other shades based on recursive noise field.

Unique Parameters: swirl_field: a NoiseField from which the selection of the shade is made depth: Number of recursive calls to make from swirl_field.noise (defaults to 0) feedback: Affect of recursive calls from swirl_field.noise shades: this one is very specific, and determines when shades are used. must be list of tuples of this form: (lower_bound, upper_bound, Shade)

because the 'shades' arguments potentially confusing, here's an example. The below will color white when noise of 0 - 0.5 is returned, and black if noise of 0.5 - 1 [(0, 0.5, shades.BlockColor((255, 255, 255)), (0.5, 1, shades.BlockColor((0, 0, 0)))]

SwirlOfShades( shades: List[Tuple[float, float, Shade]], warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0, color_variance: int = 70, swirl_field: shades.noise_fields.NoiseField = <shades.noise_fields.NoiseField object>, depth: int = 1, feedback: float = 0.7) View Source

    465    def __init__(
    466            self,
    467            shades: List[Tuple[float, float, Shade]],
    468            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    469            warp_size: int = 0,
    470            color_variance: int = 70,
    471            swirl_field: NoiseField = NoiseField(),
    472            depth: int = 1,
    473            feedback: float = 0.7,
    474        ):
    475        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    476        self.color_variance = color_variance
    477        self.swirl_field = swirl_field
    478        self.depth = depth
    479        self.feedback = feedback
    480        self.shades = shades

color_variance
swirl_field
depth
feedback
shades
def determine_shade(self, xy_coords: Tuple[int, int]): View Source

    483    def determine_shade(self, xy_coords: Tuple[int, int]):
    484        """
    485        Determines shade based on xy coordinates.
    486        """
    487        noise = self.swirl_field.recursive_noise(xy_coords, self.depth, self.feedback)
    488        shades = [i for i in self.shades if i[0] <= noise < i[1]]
    489        if len(shades) > 0:
    490            shade = shades[0][2]
    491            return shade.determine_shade(xy_coords)
    492        return None

Determines shade based on xy coordinates.

##### Inherited Members

Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class LinearGradient(Shade): View Source

    495class LinearGradient(Shade):
    496    """
    497    Type of shade that will determine color based on transition between various 'color_points'
    498
    499    Unique Parameters:
    500    color_points: Groups of colours and coordinate at which they should appear
    501    axis: 0 for horizontal gradient, 1 for vertical
    502
    503    Here's an example of color_points
    504    in this, anything before 50 (on whichever axis specified) will be black,
    505    anything after 100 will be white
    506    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    507    [((0, 0, 0), 50), ((250, 250, 250), 100)]
    508    """
    509
    510    def __init__(
    511            self,
    512            color_points: List[Tuple[int, Tuple[int, int, int]]],
    513            axis: int = 0,
    514            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    515            warp_size: int = 0,
    516        ):
    517        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    518        self.color_points = color_points
    519        self.axis = axis
    520
    521
    522    def determine_shade(self, xy_coords):
    523        """
    524        Determines shade based on xy coordinates.
    525
    526        Parameters:
    527        xy (iterable): xy coordinates
    528
    529        Returns:
    530        color in form of tuple
    531        """
    532        larger = [i[1] for i in self.color_points if i[1] >= xy_coords[self.axis]]
    533        smaller = [i[1] for i in self.color_points if i[1] < xy_coords[self.axis]]
    534        if len(smaller) == 0:
    535            next_item = min(larger)
    536            next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    537            return next_color
    538        if len(larger) == 0:
    539            last_item = max(smaller)
    540            last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    541            return last_color
    542
    543        next_item = min(larger)
    544        last_item = max(smaller)
    545
    546        next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    547        last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    548        distance_from_next = abs(next_item - xy_coords[self.axis])
    549        distance_from_last = abs(last_item - xy_coords[self.axis])
    550        from_last_to_next = distance_from_last / (distance_from_next + distance_from_last)
    551
    552        color = [0 for i in len(next_color)]
    553        for i, _ in enumerate(next_color):
    554            color_difference = (
    555                last_color[i] - next_color[i]) * from_last_to_next
    556            color[i] = last_color[i] - color_difference
    557
    558        return color_clamp(color)

Type of shade that will determine color based on transition between various 'color_points'

Unique Parameters: color_points: Groups of colours and coordinate at which they should appear axis: 0 for horizontal gradient, 1 for vertical

Here's an example of color_points in this, anything before 50 (on whichever axis specified) will be black, anything after 100 will be white between 50 and 100 will be grey, with tone based on proximity to 50 or 100 [((0, 0, 0), 50), ((250, 250, 250), 100)]

LinearGradient( color_points: List[Tuple[int, Tuple[int, int, int]]], axis: int = 0, warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0) View Source

    510    def __init__(
    511            self,
    512            color_points: List[Tuple[int, Tuple[int, int, int]]],
    513            axis: int = 0,
    514            warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    515            warp_size: int = 0,
    516        ):
    517        super().__init__(warp_noise=warp_noise, warp_size=warp_size)
    518        self.color_points = color_points
    519        self.axis = axis

color_points
axis
def determine_shade(self, xy_coords): View Source

    522    def determine_shade(self, xy_coords):
    523        """
    524        Determines shade based on xy coordinates.
    525
    526        Parameters:
    527        xy (iterable): xy coordinates
    528
    529        Returns:
    530        color in form of tuple
    531        """
    532        larger = [i[1] for i in self.color_points if i[1] >= xy_coords[self.axis]]
    533        smaller = [i[1] for i in self.color_points if i[1] < xy_coords[self.axis]]
    534        if len(smaller) == 0:
    535            next_item = min(larger)
    536            next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    537            return next_color
    538        if len(larger) == 0:
    539            last_item = max(smaller)
    540            last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    541            return last_color
    542
    543        next_item = min(larger)
    544        last_item = max(smaller)
    545
    546        next_color = [i[0] for i in self.color_points if i[1] == next_item][0]
    547        last_color = [i[0] for i in self.color_points if i[1] == last_item][0]
    548        distance_from_next = abs(next_item - xy_coords[self.axis])
    549        distance_from_last = abs(last_item - xy_coords[self.axis])
    550        from_last_to_next = distance_from_last / (distance_from_next + distance_from_last)
    551
    552        color = [0 for i in len(next_color)]
    553        for i, _ in enumerate(next_color):
    554            color_difference = (
    555                last_color[i] - next_color[i]) * from_last_to_next
    556            color[i] = last_color[i] - color_difference
    557
    558        return color_clamp(color)

Determines shade based on xy coordinates.

Parameters: xy (iterable): xy coordinates

Returns: color in form of tuple

##### Inherited Members

Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class VerticalGradient(LinearGradient): View Source

    561class VerticalGradient(LinearGradient):
    562    """
    563    Type of shade that will determine color based on transition between various 'color_points'
    564
    565    Unique Parameters:
    566    color_points: Groups of colours and coordinate at which they should appear
    567
    568    Here's an example of color_points
    569    in this, anything before 50 (on y axis) will be black,
    570    anything after 100 will be white
    571    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    572    """
    573    def __init__(
    574        self,
    575        color_points: List[Tuple[int, Tuple[int, int, int]]],
    576        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    577        warp_size: int = 0,
    578    ):
    579        super().__init__(
    580            color_points=color_points,
    581            axis=1,
    582            warp_noise=warp_noise,
    583            warp_size=warp_size,
    584        )

Type of shade that will determine color based on transition between various 'color_points'

Unique Parameters: color_points: Groups of colours and coordinate at which they should appear

Here's an example of color_points in this, anything before 50 (on y axis) will be black, anything after 100 will be white between 50 and 100 will be grey, with tone based on proximity to 50 or 100

VerticalGradient( color_points: List[Tuple[int, Tuple[int, int, int]]], warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0) View Source

    573    def __init__(
    574        self,
    575        color_points: List[Tuple[int, Tuple[int, int, int]]],
    576        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    577        warp_size: int = 0,
    578    ):
    579        super().__init__(
    580            color_points=color_points,
    581            axis=1,
    582            warp_noise=warp_noise,
    583            warp_size=warp_size,
    584        )

##### Inherited Members

LinearGradient
  color_points
  axis
  determine_shade
Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice

class HorizontalGradient(LinearGradient): View Source

    587class HorizontalGradient(LinearGradient):
    588    """
    589    Type of shade that will determine color based on transition between various 'color_points'
    590
    591    Unique Parameters:
    592    color_points: Groups of colours and coordinate at which they should appear
    593
    594    Here's an example of color_points
    595    in this, anything before 50 (on x axis) will be black,
    596    anything after 100 will be white
    597    between 50 and 100 will be grey, with tone based on proximity to 50 or 100
    598    """
    599
    600    def __init__(self,
    601        color_points: List[Tuple[int, Tuple[int, int, int]]],
    602        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    603        warp_size: int = 0,
    604    ):
    605        super().__init__(
    606            color_points=color_points,
    607            axis=0,
    608            warp_noise=warp_noise,
    609            warp_size=warp_size,
    610        )

Type of shade that will determine color based on transition between various 'color_points'

Unique Parameters: color_points: Groups of colours and coordinate at which they should appear

Here's an example of color_points in this, anything before 50 (on x axis) will be black, anything after 100 will be white between 50 and 100 will be grey, with tone based on proximity to 50 or 100

HorizontalGradient( color_points: List[Tuple[int, Tuple[int, int, int]]], warp_noise: Tuple[shades.noise_fields.NoiseField, shades.noise_fields.NoiseField] = [<shades.noise_fields.NoiseField object>, <shades.noise_fields.NoiseField object>], warp_size: int = 0) View Source

    600    def __init__(self,
    601        color_points: List[Tuple[int, Tuple[int, int, int]]],
    602        warp_noise: Tuple[NoiseField, NoiseField] = noise_fields(channels=2),
    603        warp_size: int = 0,
    604    ):
    605        super().__init__(
    606            color_points=color_points,
    607            axis=0,
    608            warp_noise=warp_noise,
    609            warp_size=warp_size,
    610        )

##### Inherited Members

LinearGradient
  color_points
  axis
  determine_shade
Shade
  color
  warp_noise
  warp_size
  adjust_point
  point
  in_bounds
  weighted_point
  pixels_inside_edge
  pixels_between_two_points
  line
  fill
  get_shape_edge
  shape
  shape_outline
  rectangle
  square
  triangle
  triangle_outline
  get_circle_edge
  circle
  circle_outline
  circle_slice