def rect_mode(mode='CORNER'):
    """Change the rect and square drawing mode for the p5.renderer.

    :param mode: The new mode for drawing rects. Should be one of
        {'CORNER', 'CORNERS', 'CENTER', 'RADIUS'}. This defaults to
        'CORNER' so calling rect_mode without parameters will reset
        the sketch's rect mode.
    :type mode: str

    """
    global _rect_mode
    _rect_mode = mode def ellipse_mode(mode='CENTER'):
    """Change the ellipse and circle drawing mode for the p5.renderer.

    :param mode: The new mode for drawing ellipses. Should be one of
        {'CORNER', 'CORNERS', 'CENTER', 'RADIUS'}. This defaults to
        'CENTER' so calling ellipse_mode without parameters will reset
        the sketch's ellipse mode.
    :type mode: str

    """
    global _ellipse_mode
    _ellipse_mode = mode def push_matrix():
    """Pushes the current transformation matrix onto the matrix stack.
    """
    matrix_stack.append(p5.renderer.transform_matrix.copy())
    return _MatrixContext() def pop_matrix():
    """Pops the current transformation matrix off the matrix stack.
    """
    assert len(matrix_stack) > 0, "No matrix to pop"
    p5.renderer.transform_matrix = matrix_stack.pop() def reset_matrix():
    """Reset the current transform matrix.
    """
    p5.renderer.transform_matrix = np.identity(4) def reset_transforms():
    """Reset all transformations to their default state.

    """
    p5.renderer.transform_matrix = np.identity(4) def shear_x(theta):
    """Shear display along the x-axis.

    :param theta: angle to shear by (in radians)
    :type theta: float

    :returns: The shear matrix used to apply the tranformation.
    :rtype: np.ndarray

    """
    shear_mat = np.identity(4)
    shear_mat[0, 1] = np.tan(theta)
    p5.renderer.transform_matrix = p5.renderer.transform_matrix.dot(shear_mat)
    return shear_mat def shear_y(theta):
    """Shear display along the y-axis.

    :param theta: angle to shear by (in radians)
    :type theta: float

    :returns: The shear matrix used to apply the transformation.
    :rtype: np.ndarray

    """
    shear_mat = np.identity(4)
    shear_mat[1, 0] = np.tan(theta)
    p5.renderer.transform_matrix = p5.renderer.transform_matrix.dot(shear_mat)
    return shear_mat def rotate_x(theta):
    """Rotate the view along the x axis.

    :param theta: angle by which to rotate (in radians)
    :type theta: float

    :returns: The rotation matrix used to apply the transformation.
    :rtype: np.ndarray

    """
    rotate(theta, axis=np.array([1, 0, 0])) def rotate_y(theta):
    """Rotate the view along the y axis.

    :param theta: angle by which to rotate (in radians)
    :type theta: float

    :returns: The rotation matrix used to apply the transformation.
    :rtype: np.ndarray

   """
    rotate(theta, axis=np.array([0, 1, 0])) def rotate_z(theta):
    """Rotate the view along the z axis.

    :param theta: angle by which to rotate (in radians)
    :type theta: float

    :returns: The rotation matrix used to apply the transformation.
    :rtype: np.ndarray

   """
    rotate(theta, axis=np.array([0, 0, 1])) def apply_matrix(transform_matrix):
    """Apply the given matrix to the sketch's transform matrix..

    :param transform_matrix: The new transform matrix.
    :type transform_matrix: np.ndarray (or a 4×4 list)
    """
    tmatrix = np.array(transform_matrix)
    p5.renderer.transform_matrix = p5.renderer.transform_matrix.dot(tmatrix) def print_matrix():
    """Print the transform matrix being used by the sketch.
    """
    print(p5.renderer.transform_matrix) def color_mode(mode, max_1=255, max_2=None, max_3=None, max_alpha=255):
    """Set the color mode of the renderer.

    :param mode: One of {'RGB', 'HSB'} corresponding to Red/Green/Blue
        or Hue/Saturation/Brightness
    :type mode: str

    :param max_1: Maximum value for the first color channel (default:
        255)
    :type max_1: int

    :param max_2: Maximum value for the second color channel (default:
        max_1)
    :type max_2: int

    :param max_3: Maximum value for the third color channel (default:
        max_1)
    :type max_3: int

    :param max_alpha: Maximum value for the alpha channel (default:
        255)
    :type max_alpha: int

    """
    global color_parse_mode
    global color_range

    if max_2 is None:
        max_2 = max_1

    if max_3 is None:
        max_3 = max_1

    color_range = (max_1, max_2, max_3, max_alpha)
    color_parse_mode = mode def no_fill():
    """Disable filling geometry."""
    p5.renderer.style.fill_enabled = False def no_tint():
    """Disable tinting of images."""
    p5.renderer.style.tint_enabled = False def no_stroke():
    """Disable drawing the stroke around shapes."""
    p5.renderer.style.stroke_enabled = False def stroke_cap(c):
    """Sets the style of line endings. The ends are SQUARE,
    PROJECT, and ROUND. The default cap is ROUND.

    :param c: either 'SQUARE', 'PROJECT' or 'ROUND'
    :type c: string

    """
    if c in [SQUARE, PROJECT, ROUND]:
        p5.renderer.stroke_cap = c
    else:
        raise ValueError("Invalid Stroke Cap %s" % c) def stroke_join(j):
    """Sets the style of the joints which connect line segments.
    These joints are either mitered, beveled, or rounded and
    specified with the corresponding parameters MITER, BEVEL,
    and ROUND. The default joint is MITER.

    :param weight: either 'MITER', 'BEVEL' or 'ROUND'
    :type j: string

    """
    if j in [MITER, BEVEL, ROUND]:
        p5.renderer.stroke_join = j
    else:
        raise ValueError("Invalid Stroke Cap %s" % j) def stroke_weight(thickness):
    """Sets the width of the stroke used for lines, points, and the border around shapes. All widths are set in units of pixels.

    :param weight: thickness of stroke in pixels
    :type weight: int

    """
    p5.renderer.stroke_weight = thickness def load_image(filename):
    """Load an image from the given filename (or URL).

    Loads an image into a variable of type PImage. Four types of
    images may be loaded.

    In most cases, load all images in setup() or outside the draw()
    call to preload them at the start of the program. Loading images
    inside draw() will reduce the speed of a program.

    :param filename: Filename (or path or URL) of the given image. The
        file-extennsion is automatically inferred.
    :type filename: str

    :returns: An :class:`p5.PImage` instance with the given image data
    :rtype: :class:`p5.PImage`

    """
    if (re.match(r'\w+://', filename)):
        with urllib.request.urlopen(filename) as url:
            f = io.BytesIO(url.read())
            img = Image.open(f)
    else:
        img = Image.open(filename)
    w, h = img.size
    pimg = PImage(w, h)
    pimg._img = img

    return pimg def load_pixels(self):
    """Load internal pixel data for the image.

    By default image data is only loaded lazily, i.e., right
    before displaying an image on the screen. Use this method to
    manually load the internal image data.

    """
    self._load() def save_frame(filename=None):
    if filename:
        p5.sketch.screenshot(filename)
    else:
        p5.sketch.screenshot("Screen.png") def image_mode(mode):
    """Modify the locaton from which the images are drawn.

    Modifies the location from which images are drawn by changing the
    way in which parameters given to :meth:`p5.image` are intepreted.

    The default mode is ``image_mode('corner')``, which interprets the
    second parameter of ``image()`` as the upper-left corner of the
    image. If an additional parameter is specified, it is used to set
    the image's width and height.

    ``image_mode('corners')`` interprets the first parameter of
    ``image()`` as the location of one corner, and the second
    parameter as the opposite corner.

    ``image_mode('center')`` interprets the first parameter of
    ``image()`` as the image's center point. If an additional
    parameter is specified, it is used to set the width and height of
    the image.

    :param mode: should be one of ``{'corner', 'center', 'corners'}``
    :type mode: str

    :raises ValueError: When the given image mode is not understood.
        Check for typoes.

    """
    global _image_mode

    if mode.lower() not in ['corner', 'center', 'corners']:
        raise ValueError("Unknown image mode!")
    _image_mode = mode.lower() def create_font(name, size=10):
    """Create the given font at the appropriate size.

    :param name: Filename of the font file (only pil, otf and ttf
        fonts are supported.)
    :type name: str

    :param size: Font size (only required when `name` refers to a
        truetype font; defaults to None)
    :type size: int | None

    """

    if name.endswith('ttf') or name.endswith('otf'):
        font = ImageFont.truetype(name, size)
    elif name.endswith('pil'):
        font = ImageFont.load(name)
    else:
        raise NotImplementedError("Font type not supported.")

    return font def load_font(font_name):
    """Loads the given font into a font object

    """
    return create_font(font_name) def text_font(font, size=10):
    """Set current text font.

    :param font:
    :type font: PIL.ImageFont.ImageFont

    """
    global _font_family
    _font_family = font def text_size(size):
    """Sets the current font size

    :param leading: the size of the letters in units of pixels
    :type align_x: int

    """

    global _font_family

    # reload the font with new size
    if hasattr(_font_family, 'path'):
        if _font_family.path.endswith(
                'ttf') or _font_family.path.endswith('otf'):
            _font_family = ImageFont.truetype(_font_family.path, size)
    else:
        raise ValueError("text_size is not supported for Bitmap Fonts") def text_width(text):
    """Calculates and returns the width of any character or text string

    :param text_string: text
    :type text_string: str

    :returns: width of any character or text string
    :rtype: int

    """

    return _font_family.getsize(text)[0] def text_descent():
    """Returns descent of the current font at its current size

    :returns:  descent of the current font at its current size
    :rtype: float

    """

    global _font_family
    ascent, descent = _font_family.getmetrics()
    return descent def text_align(align_x, align_y=None):
    """Set the alignment of drawing text

    :param align_x: "RIGHT", "CENTER" or "LEFT".
    :type align_x: string

    :param align_y: "TOP", "CENTER" or "BOTTOM".
    :type align_y: string

    """

    global _text_align_x, _text_align_y
    _text_align_x = align_x

    if align_y:
        _text_align_y = align_y def text_ascent():
    """Returns ascent of the current font at its current size

    :returns: ascent of the current font at its current size
    :rtype: float

    """
    global _font_family
    ascent, descent = _font_family.getmetrics()
    return ascent def text_leading(leading):
    """Sets the spacing between lines of text in units of pixels

    :param leading: the size in pixels for spacing between lines
    :type align_x: int

    """

    global _text_leading
    _text_leading = leading TESS = 'TESS' def begin_shape(kind=TESS):
    """Begin shape drawing.  This is a helpful way of generating custom shapes quickly.

    :param kind: TESS, POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, or QUAD_STRIP; defaults to TESS
    :type kind: SType
    """
    global shape_kind, vertices, contour_vertices, vertices_types, contour_vertices_types, is_contour
    global curr_contour_vertices, curr_contour_vertices_types

    shape_kind = kind
    is_contour = False
    vertices = []
    vertices_types = []
    contour_vertices = []
    contour_vertices_types = []
    curr_contour_vertices = []
    curr_contour_vertices_types = [] def begin_contour():
    """
    Use the beginContour() and endContour() functions
    to create negative shapes within shapes such as
    the center of the letter 'O'. beginContour() begins
    recording vertices for the shape and endContour() stops
    recording. The vertices that define a negative shape must
    "wind" in the opposite direction from the exterior shape.
    First draw vertices for the exterior clockwise order, then
    for internal shapes, draw vertices shape in counter-clockwise.

    """
    global is_contour, contour_vertices, contour_vertices_types
    is_contour = True
    contour_vertices = []
    contour_vertices_types = [] def curve_vertex(x, y, z=0):
    """
    Specifies vertex coordinates for curves. The first
    and last points in a series of curveVertex() lines
    will be used to guide the beginning and end of a the
    curve. A minimum of four points is required to draw a
    tiny curve between the second and third points. Adding
    a fifth point with curveVertex() will draw the curve
    between the second, third, and fourth points. The
    curveVertex() function is an implementation of
    Catmull-Rom splines.

    :param x: x-coordinate of the vertex
    :type x: float

    :param y: y-coordinate of the vertex
    :type y: float

    :param z: z-coordinate of the vertex
    :type z: float
    """

    global is_curve
    is_curve = True

    if p5.mode == "3D":
        return
    else:
        if is_contour:
            curr_contour_vertices.append((x, y, z))
            curr_contour_vertices_types.append(2)
        else:
            vertices.append((x, y, z))  # False attribute if the vertex is
            vertices_types.append(2) def bezier_vertex(x2, y2, x3, y3, x4, y4):
    """
    Specifies vertex coordinates for Bezier curves

    :param x2: x-coordinate of the first control point
    :type x2: float

    :param y2: y-coordinate of the first control point
    :type y2: float

    :param x3: x-coordinate of the second control point
    :type x3: float

    :param y3: y-coordinate of the second control point
    :type y3: float

    :param x4: x-coordinate of the anchor point
    :type x4: float

    :param y4: y-coordinate of the anchor point
    :type y4: float
    """
    global is_bezier
    is_bezier = True

    if p5.mode == "3D":
        return
    else:
        if is_contour:
            curr_contour_vertices.append((x2, y2, x3, y3, x4, y4))
            curr_contour_vertices_types.append(3)
        else:
            vertices.append((x2, y2, x3, y3, x4, y4))
            vertices_types.append(3) def end_contour():
    """Ends the current contour.

    For more info, see :any:`begin_contour`.
    """
    global is_contour, curr_contour_vertices, curr_contour_vertices_types
    is_contour = False
    # Close contour
    curr_contour_vertices.append(curr_contour_vertices[0])
    curr_contour_vertices_types.append(curr_contour_vertices_types[0])
    # Save contour
    contour_vertices.append(curr_contour_vertices)
    contour_vertices_types.append(curr_contour_vertices_types)
    curr_contour_vertices, curr_contour_vertices_types = [], [] def end_shape(mode=""):
    """
    The endShape() function is the companion to beginShape()
    and may only be called after beginShape(). When endshape()
    is called, all of image data defined since the previous call
    to beginShape() is rendered.

    :param mode: use CLOSE to close the shape
    :type mode: str

    """
    global is_bezier, is_curve, is_quadratic, is_contour
    assert not is_contour, "begin_contour called without calling end_contour"
    if is_curve or is_bezier or is_quadratic:
        assert shape_kind == TESS, "Should not specify primitive type for a curve"

    if len(vertices) == 0:
        return

    if (not p5.renderer.style.stroke_enabled) and (
            not p5.renderer.style.fill_enabled):
        return

    # if the shape is closed, the first element is also the last element
    if mode == 'CLOSE':
        vertices.append(vertices[0])
        vertices_types.append(vertices_types[0])

    if is_curve:
        if len(vertices) > 3:
            p5.renderer.shape(vertices=get_curve_vertices(vertices),
                              contours=[
                                  get_curve_vertices(c) for c in contour_vertices],
                              shape_type=TESS)
    elif is_bezier:
        p5.renderer.shape(vertices=get_bezier_vertices(vertices, vertices_types),
                          contours=[get_bezier_vertices(contour_vertices[i], contour_vertices_types[i])
                                    for i in range(len(contour_vertices))],
                          shape_type=TESS)
    elif is_quadratic:
        p5.renderer.shape(vertices=get_quadratic_vertices(vertices, vertices_types),
                          contours=[get_quadratic_vertices(contour_vertices[i], contour_vertices_types[i])
                                    for i in range(len(contour_vertices))],
                          shape_type=TESS)
    else:
        p5.renderer.shape(
            vertices=vertices,
            contours=contour_vertices,
            shape_type=shape_kind)

    is_bezier = False
    is_curve = False
    is_quadratic = False
    is_contour = False def quadratic_vertex(cx, cy, x3, y3):
    """
    Specifies vertex coordinates for quadratic Bezier curves

    :param cx: x-coordinate of the control point
    :type cx: float

    :param cy: y-coordinate of the control point
    :type cy: float

    :param x3: x-coordinate of the anchor point
    :type x3: float

    :param y3: y-coordinate of the anchor point
    :type y3: float

    """
    global is_quadratic
    is_quadratic = True

    if p5.mode == "3D":
        return
    else:
        if is_contour:
            curr_contour_vertices.append((cx, cy, x3, y3))
            curr_contour_vertices_types.append(4)
        else:
            vertices.append((cx, cy, x3, y3))
            vertices_types.append(3) def normal_material():
    """The color is determined by the normal vector of a surface. Does not respond to lights.

    Useful for debugging.
    """
    ensure_p3d("normal_material")
    p5.renderer.style.material = NormalMaterial() def basic_material(r, g, b):
    """The default material. Always displays a solid color.

    :param r: red channel
    :type r: float

    :param g: green channel
    :type g: float

    :param b: blue channel
    :type b: float
    """
    ensure_p3d("basic_material")
    fill(r, g, b)
    p5.renderer.style.material = BasicMaterial(p5.renderer.style.fill_color) def blinn_phong_material():
    """Material based on the Blinn-Phong reflection model. This is the most "realistic" material in p5py.

    Blinn-Phong shading can be decomposed into three parts:
    ambient, diffuse, and specular.

    The ambient component is essentially a constant term that is alway present.
    We calculate it by summing all the ambient lights in a scene and multiplying it
    with the normalized ambient coefficent set by ambient.

    The diffuse component takes the normal vector of a surface into account and
    varies how much light is reflected depending on the angle that the surface
    makes with the incoming light.

    The specular component not only accounts for the direction of the light
    (like the diffuse component) but also the direction of the viewer. If the
    viewer is not on the path of the reflected light, the specular component
    falls off quickly, producing the glossy reflections we see on some materials.

    The color shown on the user's screen is the sum of all three components.
    """
    ensure_p3d("blinn_phong_material")
    rend = p5.renderer.style
    p5.renderer.style.material = BlinnPhongMaterial(
        rend.ambient, rend.diffuse, rend.specular, rend.shininess) def ambient_light(r, g, b):
    """Adds an ambient light.

    Ambient light comes from all directions towards all directions.
    Ambient lights are almost always used in combination with other types of lights.

    :param r: red channel
    :type r: float

    :param g: green channel
    :type g: float

    :param b: blue channel
    :type b: float
    """
    ensure_p3d('ambient_light')
    p5.renderer.add_ambient_light(*scale_tuple((r, g, b))) def directional_light(r, g, b, x, y, z):
    """Adds a directional light.
    Directional light comes from one direction: it is stronger when hitting a surface squarely,
    and weaker if it hits at a gentle angle.
    After hitting a surface, directional light scatters in all directions.

    :param r: red channel
    :type r: float

    :param g: green channel
    :type g: float

    :param b: blue channel
    :type b: float

    :param x: x component of the direction vector
    :type x: float

    :param y: y component of the direction vector
    :type y: float

    :param z: z component of the direction vector
    :type z: float
    """
    ensure_p3d('directional_light')
    p5.renderer.add_directional_light(*scale_tuple((r, g, b)), x, y, z) def point_light(r, g, b, x, y, z):
    """Adds a point light.
    Point light comes from one location and emits to all directions.

    :param r: red channel
    :type r: float

    :param g: green channel
    :type g: float

    :param b: blue channel
    :type b: float

    :param x: x component of the location vector
    :type x: float

    :param y: y component of the location vector
    :type y: float

    :param z: z component of the location vector
    :type z: float
    """
    ensure_p3d('point_light')
    p5.renderer.add_point_light(*scale_tuple((r, g, b)), x, y, z) def light_falloff(constant, linear, quadratic):
    """Sets the falloff rates for point lights. Affects only the elements which are created after it in the code.

    d = distance from light position to vertex position

    falloff = 1 / (constant + d * linear + (d*d) * quadratic)

    If the coefficient is 0, then that term is ignored.
    The P3D renderer defaults to (0, 0, 0), i.e. no falloff.

    :param constant: coefficient for the constant term
    :type constant: float

    :param linear: coefficient for the linear term
    :type linear: float

    :param quadratic: coefficient for the quadratic term
    :type quadratic: float
    """
    ensure_p3d('light_falloff')
    p5.renderer.curr_constant_falloff = constant
    p5.renderer.curr_linear_falloff = linear
    p5.renderer.curr_quadratic_falloff = quadratic def light_specular(r, g, b):
    """Sets the specular color for lights. Only visible with :any:`p5.blinn_phong_material`. Is set to (0 ,0, 0) by default.
    Affects only the elements which are created after it in the code.

    Specular refers to light which bounces off a surface in a preferred direction
    (rather than bouncing in all directions like a diffuse light) and is used for
    creating highlights. The specular quality of a light interacts with the specular
    material qualities set through the `specular()` and `shininess()` functions.

    :param r: red channel
    :type r: float

    :param g: green channel
    :type g: float

    :param b: blue channel
    :type b: float
    """
    ensure_p3d('light_specular')
    p5.renderer.light_specular = np.array(scale_tuple((r, g, b)))
from .structure import push_style, pop_style
from .primitives import rect_mode, ellipse_mode
from .transforms import push_matrix, pop_matrix, reset_matrix, reset_transforms, shear_x, shear_y, \
    rotate_x, rotate_y, rotate_z, apply_matrix, print_matrix
from .color import color_mode
from .attribs import no_fill, no_tint, no_stroke, stroke_cap, stroke_join, stroke_weight
from .image import load_image, load_pixels, save_frame, image_mode
from .font import create_font, load_font, text_font, text_size, text_width, text_descent, text_align, \
    text_ascent, text_leading
from .constants import TESS
from .vertex import begin_shape, begin_contour, curve_vertex, bezier_vertex, end_contour, end_shape, quadratic_vertex
from .material import normal_material, basic_material, blinn_phong_material
from .light import ambient_light, directional_light, point_light, light_falloff, light_specular


def imageMode(mode):
    """Modify the locaton from which the images are drawn.

    Modifies the location from which images are drawn by changing the
    way in which parameters given to :meth:`p5.image` are intepreted.

    The default mode is ``image_mode('corner')``, which interprets the
    second parameter of ``image()`` as the upper-left corner of the
    image. If an additional parameter is specified, it is used to set
    the image's width and height.

    ``image_mode('corners')`` interprets the first parameter of
    ``image()`` as the location of one corner, and the second
    parameter as the opposite corner.

    ``image_mode('center')`` interprets the first parameter of
    ``image()`` as the image's center point. If an additional
    parameter is specified, it is used to set the width and height of
    the image.

    :param mode: should be one of ``{'corner', 'center', 'corners'}``
    :type mode: str

    :raises ValueError: When the given image mode is not understood.
        Check for typoes.

    """
    image_mode(mode)


def loadImage(filename):
    """Load an image from the given filename.

    Loads an image into a variable of type PImage. Four types of
    images may be loaded.

    In most cases, load all images in setup() or outside the draw()
    call to preload them at the start of the program. Loading images
    inside draw() will reduce the speed of a program.

    :param filename: Filename (or path)of the given image. The
        file-extennsion is automatically inferred.
    :type filename: str

    :returns: An :class:`p5.PImage` instance with the given image data
    :rtype: :class:`p5.PImage`

    """
    return load_image(filename)


def loadPixels():
    """Load a snapshot of the display window into the ``pixels`` Image.

    This context manager loads data into the global ``pixels`` Image.
    Once the program execution leaves the context manager, all changes
    to the image are written to the main display.

    """
    load_pixels()


def saveFrame(filename=None):
    save_frame(filename)


def createFont(name, size=10):
    """Create the given font at the appropriate size.

    :param name: Filename of the font file (only pil, otf and ttf
        fonts are supported.)
    :type name: str

    :param size: Font size (only required when `name` refers to a
        truetype font; defaults to None)
    :type size: int | None

    """
    return create_font(name, size)


def loadFont(font_name):
    """Loads the given font into a font object

    """
    return load_font(font_name)


def textFont(font, size=10):
    """Set current text font.

    :param font:
    :type font: PIL.ImageFont.ImageFont

    """
    text_font(font, size)


def textAlign(alignX, alignY=None):
    """Set the alignment of drawing text

    :param alignX: "RIGHT", "CENTER" or "LEFT".
    :type alignX: string

    :param alignY: "TOP", "CENTER" or "BOTTOM".
    :type alignY: string

    """
    text_align(alignX, alignY)


def textLeading(leading):
    """Sets the spacing between lines of text in units of pixels

    :param leading: the size in pixels for spacing between lines
    :type align_x: int

    """

    text_leading(leading)