Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic surface size #5

Merged
merged 12 commits into from
Sep 9, 2018
206 changes: 127 additions & 79 deletions contextfree/contextfree.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@
import random
import math
import colorsys
import logging
import numpy as np
import cairocffi as cairo


logger = logging.getLogger(__name__)

MAX_ELEMENTS = 200000
MAX_DEPTH = 8
HEIGHT = 100
WIDTH = 100
SIZE_MIN_FEATURE = 0.5
_state = {}
_ctx = None
_background_color = None


def _init__state():
Expand All @@ -33,13 +38,125 @@ def surface_to_image(surface):
return Image(data=data)


def display_ipython():
"""renders global surface to IPython notebook"""
image_surface = render_record_surface()
return surface_to_image(image_surface)


def get_npimage(transparent=False, y_origin="top"):
""" Returns a WxHx[3-4] numpy array representing the RGB picture.

If `transparent` is True the image is WxHx4 and represents a RGBA picture,
i.e. array[i,j] is the [r,g,b,a] value of the pixel at position [i,j].
If `transparent` is false, a RGB array is returned.

Parameter y_origin ("top" or "bottom") decides whether point (0,0) lies in
the top-left or bottom-left corner of the screen.
"""
image_surface = render_record_surface()
img = 0 + np.frombuffer(image_surface.get_data(), np.uint8)
img.shape = (HEIGHT, WIDTH, 4)
img = img[:, :, [2, 1, 0, 3]]
if y_origin == "bottom":
img = img[::-1]
return img if transparent else img[:, :, : 3]


def render_record_surface():
# image_surface = cairo.SVGSurface(None, HEIGHT, WIDTH)
x_start, y_start, width_actual, height_actual = surface.ink_extents()
# print(x_start, y_start, width_actual, height_actual)
# shrink and translate to match specified width and height
image_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
context = cairo.Context(image_surface)
scale = min(WIDTH / width_actual, HEIGHT / height_actual)
if _background_color is not None:
logger.info("filling background_color")
source = context.get_source()
pat = cairo.SolidPattern(* htmlcolor_to_rgb(_background_color))
context.rectangle(0, 0, WIDTH, HEIGHT) # Rectangle(x0, y0, x1, y1)
context.set_source(pat)
context.fill()
context.set_source(source)
context.translate(WIDTH / 2, HEIGHT / 2)
context.scale(scale, -scale) # Normalizing the canvas
context.translate(-x_start - width_actual / 2, -y_start - height_actual / 2)
context.set_source_surface(surface, 0, 0)
context.paint()
return image_surface


def write_to_png(*args, **kwargs):
"""Saves current buffer surface to png file"""
return surface.write_to_png(*args, **kwargs)
image_surface = render_record_surface()
return image_surface.write_to_png(*args, **kwargs)


def check_limits(some_function):
"""Stop recursion if resolution is too low on number of components is too high """

def wrapper(*args, **kwargs):
"""The body of the decorator """
global _state
_state["cnt_elements"] += 1
_state["depth"] += 1
matrix = _ctx.get_matrix()
# print(matrix)
if _state["depth"] >= MAX_DEPTH:
logger.info("stop recursion by reaching max depth")
else:
if _state["cnt_elements"] > MAX_ELEMENTS:
logger.info("stop recursion by reaching max elements")
else:
min_size_scaled = SIZE_MIN_FEATURE / min(WIDTH, HEIGHT)
current_scale = max([abs(matrix[i]) for i in range(2)])
if (current_scale < min_size_scaled):
logger.info("stop recursion by reaching min feature size")
else:
some_function(*args, **kwargs)
_state["depth"] -= 1
return wrapper


def report():
"""Prints some stats on current state"""
global _state
print("cnt elements drawn:", _state["cnt_elements"])


def init(canvas_size=(512, 512), max_depth=12, face_color=None, background_color=None):
"""Initializes global state"""
global _background_color
_background_color = background_color
global surface
global _ctx
global cnt_elements
# global depth
global MAX_DEPTH
global WIDTH
global HEIGHT
_init__state()
MAX_DEPTH = max_depth
WIDTH, HEIGHT = canvas_size
# surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
surface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, None)
_ctx = cairo.Context(surface)
# _ctx.translate(WIDTH / 2, HEIGHT / 2)
# scale = min(WIDTH, HEIGHT)
# _ctx.scale(scale, -scale) # Normalizing the canvas
# _ctx.rotate(math.pi)

if face_color is not None:
_ctx.set_source_rgb(* htmlcolor_to_rgb(face_color))
logger.debug("Init done")


# -------------------transformations------------------

class rotate:
"""Defines a scope of rotated view """

def __init__(self, angle):
self.angle = angle
self.matrix_old = None
Expand All @@ -56,6 +173,7 @@ def __exit__(self, type, value, traceback):

class translate:
"""Defines a scope of linear translation"""

def __init__(self, offset_x, offset_y):
self.offset_x = offset_x
self.offset_y = offset_y
Expand All @@ -72,6 +190,7 @@ def __exit__(self, type, value, traceback):

class scale:
"""Defines scope of changed scale"""

def __init__(self, scale_x, scale_y=None):
self.scale_x = scale_x
if scale_y is None:
Expand Down Expand Up @@ -121,8 +240,12 @@ def __enter__(self):
r, g, b, a = self.source_old.get_rgba()
hue, lightness, saturation = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
hue = math.modf(hue + self.hue)[0]
lightness = math.modf(lightness * self.lightness)[0]
saturation = math.modf(saturation * self.saturation)[0]
lightness = lightness * self.lightness
if lightness > 1:
lightness = 1
saturation = saturation * self.saturation
if saturation > 1:
saturation = 1
r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation)
a = min((a * self.alpha), 255)
rgba = [r * 255, g * 255, b * 255, a]
Expand All @@ -132,27 +255,7 @@ def __exit__(self, type, value, traceback):
global _ctx
_ctx.set_source(self.source_old)


def check_limits(some_function):
"""Stop recursion if resolution is too low on number of components is too high """

def wrapper(*args, **kwargs):
"""The body of the decorator """
global _state
_state["cnt_elements"] += 1
_state["depth"] += 1
matrix = _ctx.get_matrix()
if (abs(matrix[0]) > 0.5 and _state["cnt_elements"] < MAX_ELEMENTS and
_state["depth"] < MAX_DEPTH):
some_function(*args, **kwargs)
_state["depth"] -= 1
return wrapper


def report():
"""Prints some stats on current state"""
global _state
print("cnt elements drawn:", _state["cnt_elements"])
# ----------------- primitives ----------------------


def line(x, y, width=0.1):
Expand Down Expand Up @@ -197,61 +300,6 @@ def box(side=1):
_ctx.fill()


def init(canvas_size=(512, 512), max_depth=10, face_color=None, background_color=None):
"""Initializes global state"""
global surface
global _ctx
global cnt_elements
# global depth
global MAX_DEPTH
global WIDTH
global HEIGHT
_init__state()
MAX_DEPTH = max_depth
WIDTH, HEIGHT = canvas_size
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
_ctx = cairo.Context(surface)
_ctx.translate(WIDTH / 2, HEIGHT / 2)
scale = min(WIDTH, HEIGHT)
_ctx.scale(scale, -scale) # Normalizing the canvas
# _ctx.rotate(math.pi)

if background_color is not None:
source = _ctx.get_source()
pat = cairo.SolidPattern(* htmlcolor_to_rgb(background_color))
_ctx.rectangle(-1, -1, 2, 2) # Rectangle(x0, y0, x1, y1)
_ctx.set_source(pat)
_ctx.fill()
_ctx.set_source(source)
if face_color is not None:
_ctx.set_source_rgb(* htmlcolor_to_rgb(face_color))


def display_ipython():
"""renders global surface to IPython notebook"""
global surface
return surface_to_image(surface)


def get_npimage(transparent=False, y_origin="top"):
""" Returns a WxHx[3-4] numpy array representing the RGB picture.

If `transparent` is True the image is WxHx4 and represents a RGBA picture,
i.e. array[i,j] is the [r,g,b,a] value of the pixel at position [i,j].
If `transparent` is false, a RGB array is returned.

Parameter y_origin ("top" or "bottom") decides whether point (0,0) lies in
the top-left or bottom-left corner of the screen.
"""
global surface
img = 0 + np.frombuffer(surface.get_data(), np.uint8)
img.shape = (HEIGHT, WIDTH, 4)
img = img[:, :, [2, 1, 0, 3]]
if y_origin == "bottom":
img = img[::-1]
return img if transparent else img[:, :, : 3]


def rnd(diap):
"""returns random number in diapason from -diap to diap"""
return (random.random() - 0.5) * 2 * diap
Expand Down
18 changes: 7 additions & 11 deletions examples/branches_random.ipynb

Large diffs are not rendered by default.

18 changes: 8 additions & 10 deletions examples/branches_round.ipynb

Large diffs are not rendered by default.

27 changes: 7 additions & 20 deletions examples/koch.ipynb

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions examples/paper.ipynb

Large diffs are not rendered by default.

32 changes: 12 additions & 20 deletions examples/tree.ipynb

Large diffs are not rendered by default.

32 changes: 6 additions & 26 deletions examples/triangle.ipynb

Large diffs are not rendered by default.