Skip to content

Commit

Permalink
Add support for palette constraints (#44)
Browse files Browse the repository at this point in the history
Integrate palette constraint support art commissions. The palette generator is seeded using the Kademlia XOR distance for choosing the set of colors.
  • Loading branch information
yen-mai-codes authored Apr 29, 2024
1 parent f680ffc commit 417c412
Show file tree
Hide file tree
Showing 17 changed files with 214 additions and 266 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ruff-format:

run:
export PYTHONPATH=src/main/py; \
python3 -m peer.peer 50000 "src/test/py/resources/peer_test" "0.0.0.0:50000"
python -m peer.peer 50000 "src/test/py/resources/peer_test" "0.0.0.0:50000"

test: ruff-check pylint
export PYTHONPATH=src/main/py; \
Expand Down
10 changes: 5 additions & 5 deletions src/main/py/commission/artfragment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@

import logging
from dataclasses import dataclass
from commission.artwork import Pixel
from drawing.drawing import Pixel


@dataclass(frozen=True)
class ArtFragment:
"""
Class to create ArtFragment
- artwork (Artwork): Artwork that ArtFragment is contributing to
- contributor (str): ID of peer generating the ArtFragment
- pixels (set[Pixel]): set of pixels that fragment occupies
- artwork_id: ID of Artwork that fragment is contributing to.
- contributor_signing_key: signing key of the peer that is contributing to the artwork.
- pixels: set of pixels that fragment occupies.
"""

artwork_id: str
contributor: str
contributor_id: str
pixels: frozenset[Pixel]

logger = logging.getLogger("ArtFragment")
143 changes: 110 additions & 33 deletions src/main/py/commission/artfragmentgenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,114 @@
import random
from random import randrange
from collections import namedtuple
from commission.artwork import Artwork, Pixel
from commission.artwork import Artwork
from commission.artfragment import ArtFragment
from drawing.coordinates import Coordinates
from drawing.drawing import Coordinates, Pixel, Constraint, Color

Subcanvas = namedtuple("SubCanvas", ["coordinates", "dimensions"])
Subcanvas.__annotations__ = {"coordinates": Coordinates, "dimensions": tuple[int, int]}

logger = logging.getLogger("ArtFragmentGenerator")


def generate_fragment(artwork: Artwork, contributor: str):
"""
Generates an ArtFragment instance
- artwork (Artwork): Artwork that the ArtFragment is intended for.
- contributor (str): Peer ID that created the ArtFragment.
def generate_fragment(
artwork: Artwork,
originator_id: int,
contributor_signing_key: str,
contributor_id: int,
) -> ArtFragment:
"""Generates an ArtFragment instance
Args:
artwork: Artwork that the ArtFragment is intended for.
originator_id: ID of the peer that commissioned the artwork.
contributor_signing_key: signing key of the peer that is contributing to the artwork.
contributor_id: ID of the peer that is contributing to the artwork.
Returns:
ArtFragment: art fragment that adheres to artwork's constraint.
"""
constraint = artwork.constraint
subcanvas = generate_subcanvas(artwork.width, artwork.height)

pixels = generate_pixels(subcanvas)
fragment = ArtFragment(artwork.get_key(), contributor, pixels)
pixels = generate_pixels(originator_id, contributor_id, subcanvas, constraint)
fragment = ArtFragment(artwork.get_key(), contributor_signing_key, pixels)
return fragment


def generate_subcanvas(width: int, height: int):
def get_bounds(coordinates: Coordinates, width: int, height: int) -> tuple[int, int]:
"""Get bounds of how big a subcanvas can be.
Args:
coordinates: starting coordinates of subcanvas.
width: width of artwork.
height: height of artwork.
"""
Generates starting (x,y) coordinates with width and height adhering to artwork
bounds = (width - coordinates.x, height - coordinates.y)
return bounds


def generate_subcanvas(width: int, height: int) -> Subcanvas:
"""Generates starting (x,y) coordinates with width and height within bounds of the artwork.
Args:
width: artwork's width.
height: artwork's height.
Returns:
Subcanvas: a subcanvas that is within the artwork's width and height.
"""
x_coordinate = randrange(0, width)
y_coordinate = randrange(0, height)
coordinates = Coordinates(x_coordinate, y_coordinate)
bounds = coordinates.create_bounds(width, height)

subcanvas_width = randrange(1, bounds[0])
subcanvas_height = randrange(1, bounds[1])
bounds = get_bounds(coordinates, width, height)
subcanvas_width = randrange(1, bounds[0] + 1)
subcanvas_height = randrange(1, bounds[1] + 1)

subcanvas = Subcanvas(
coordinates=coordinates, dimensions=(subcanvas_width, subcanvas_height)
)
return subcanvas


def generate_pixels(subcanvas: tuple[tuple[int, int], tuple[int, int]]):
"""
Generate a list of pixel info that adheres to coordiantes, dimensions, constraints
def generate_pixels(
originator_id: int,
contributor_id: int,
subcanvas: Subcanvas,
constraint: Constraint = None,
) -> set:
"""Generate a list of pixel info that adheres to subcanvas and artwork's constraints.
TODO:
- implement address-based adherence
- implement line type adherence
"""
coordinates = subcanvas.coordinates
dimensions = subcanvas.dimensions
color_constraint = (
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
)
x_coordinate = coordinates.x
y_coordinate = coordinates.y
Args:
originator_id : ID of the peer that commissioned the artwork.
contributor_id: ID of the peer that is contributing to the artwork.
subcanvas: the subcanvas that the contributor will draw on.
constraint: Constraint of subcanvas. Defaults to None.
width = dimensions[0]
height = dimensions[1]
Returns:
set: a set of pixels with coordinates and colors adhering to subcanvas and constraints.
"""
if constraint is not None:
palette = get_palette(originator_id, contributor_id, constraint.palette_limit)
else:
palette = [
Color(
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
)
]

x_coordinate = subcanvas.coordinates.x
y_coordinate = subcanvas.coordinates.y

width = subcanvas.dimensions[0]
height = subcanvas.dimensions[1]

# generate the set of pixels to occupy
num_pixels = randrange(0, width * height)
x_bound = x_coordinate + width
y_bound = y_coordinate + height
Expand All @@ -82,9 +126,42 @@ def generate_pixels(subcanvas: tuple[tuple[int, int], tuple[int, int]]):
coordinates = Coordinates(
randrange(x_coordinate, x_bound), randrange(y_coordinate, y_bound)
)

pixel = Pixel(coordinates, color_constraint)
pixel = Pixel(coordinates, random.choice(palette))
set_pixels.add(pixel)
num_pixels -= 1

return set_pixels


def get_palette(originator_id: int, contributor_id: int, palette_limit: int) -> list:
"""
Get palette corresponding to palette_limit and distance from originator to contributor
Args:
originator_id: ID of the peer that commissioned the artwork.
contributor_id: ID of the peer that is contributing to the artwork.
palette_limit: the number of colors in palette
Returns:
list: a list of colors in the palette
"""

distance = originator_id ^ contributor_id
random.seed(distance)

# Create an array of palette_limit*3 color channels
channels = random.sample(range(0, 256), palette_limit * 3)

# create a palette array of colors, each color being a 3-element tuple
start = 0
end = 3
n = len(channels)
palette = []

while end <= n:
color = Color(*channels[start:end])
start = end
end += 3
palette.append(color)

return palette
38 changes: 9 additions & 29 deletions src/main/py/commission/artwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@
Artwork class allows us to create a commission, generate a file descriptor
"""

import random
from collections import namedtuple
from datetime import datetime, timedelta
from drawing.coordinates import Coordinates
from drawing.color import Color
from drawing.drawing import Constraint
from peer.ledger import Ledger
import utils

Pixel = namedtuple("Pixel", ["coordinates", "color"])
Pixel.__annotations__ = {"coordiantes": Coordinates, "color": Color}
Constraint = namedtuple("Constraint", ["color", "line_type"])
Constraint.__annotations__ = {"color": Color, "line_type": str}


class Artwork:
"""
Expand All @@ -33,41 +25,29 @@ def __init__(
ledger: Ledger,
constraint: Constraint = None,
originator_public_key: str = "",
originator_long_id: int = None,
):
"""
Initializes an instance of the Artwork class.
- width (int): The width of the artwork in pixels.
- height (float): The height of the artwork in pixels.
- wait_time (timedelta): The wait time for the artwork as a timedelta.
- constraint (Constraint): Constraint instance set to the artwork.
- width: The width of the artwork in pixels.
- height: The height of the artwork in pixels.
- wait_time: The wait time for the artwork as a timedelta.
- constraint: Constraint instance set to the artwork.
- ledger: An instance of the Ledger class.
- originator_long_id: The originator's long id.
"""
self.width = width
self.height = height
self.wait_time = wait_time
self.commission_complete = False
if constraint is None:
self.constraint = self.generate_random_constraint()
else:
self.constraint = constraint
self.constraint = constraint
self.key = utils.generate_random_sha1_hash()
start_time = datetime.now()
self.end_time = start_time + self.wait_time
self.originator_public_key = originator_public_key
self.originator_long_id = originator_long_id
self.ledger = ledger

def generate_random_constraint(self):
"""
Generates a random constraint.
"""
random_color = (
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
)
random_line_type = "solid"
return Constraint(random_color, random_line_type)

def get_remaining_time(self):
"""
Returns the remaining wait time until the artwork is complete in seconds.
Expand Down
42 changes: 0 additions & 42 deletions src/main/py/drawing/color.py

This file was deleted.

35 changes: 0 additions & 35 deletions src/main/py/drawing/coordinates.py

This file was deleted.

18 changes: 18 additions & 0 deletions src/main/py/drawing/drawing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
"""
Module to manage namedtuples related to drawing
"""

from collections import namedtuple

Coordinates = namedtuple("Coordinates", ["x", "y"])
Coordinates.__annotations__ = {"x": int, "y": int}

Color = namedtuple("Color", ["r", "g", "b", "a"], defaults=(255,) * 4)
Color.__annotations__ = {"r": int, "g": int, "b": int, "a": int}

Pixel = namedtuple("Pixel", ["coordinates", "color"])
Pixel.__annotations__ = {"coordinates": Coordinates, "color": Color}

Constraint = namedtuple("Constraint", ["palette_limit", "line_type"])
Constraint.__annotations__ = {"palette_limit": int, "line_type": str}
Loading

0 comments on commit 417c412

Please sign in to comment.