Skip to content

Commit

Permalink
Merge pull request #5 from undertherain/recording
Browse files Browse the repository at this point in the history
dynamic surface size
  • Loading branch information
undertherain committed Sep 9, 2018
2 parents 2930e27 + 233455a commit 91838b0
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 171 deletions.
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.

0 comments on commit 91838b0

Please sign in to comment.