Source code for moviepy.video.tools.drawing

"""Deals with making images (np arrays). It provides drawing
methods that are difficult to do with the existing Python libraries.
"""

import numpy as np


[docs]def blit(im1, im2, pos=None, mask=None): """Blit an image over another. Blits ``im1`` on ``im2`` as position ``pos=(x,y)``, using the ``mask`` if provided. """ if pos is None: pos = (0, 0) # pragma: no cover else: # Cast to tuple in case pos is not subscriptable. pos = tuple(pos) im2.paste(im1, pos, mask) return im2
[docs]def color_gradient( size, p1, p2=None, vector=None, radius=None, color_1=0.0, color_2=1.0, shape="linear", offset=0, ): """Draw a linear, bilinear, or radial gradient. The result is a picture of size ``size``, whose color varies gradually from color `color_1` in position ``p1`` to color ``color_2`` in position ``p2``. If it is a RGB picture the result must be transformed into a 'uint8' array to be displayed normally: Parameters ---------- size : tuple or list Size (width, height) in pixels of the final image array. p1 : tuple or list Position for the first coordinate of the gradient in pixels (x, y). The color 'before' ``p1`` is ``color_1`` and it gradually changes in the direction of ``p2`` until it is ``color_2`` when it reaches ``p2``. p2 : tuple or list, optional Position for the second coordinate of the gradient in pixels (x, y). Coordinates (x, y) of the limit point for ``color_1`` and ``color_2``. vector : tuple or list, optional A vector (x, y) in pixels that can be provided instead of ``p2``. ``p2`` is then defined as (p1 + vector). color_1 : tuple or list, optional Starting color for the gradient. As default, black. Either floats between 0 and 1 (for gradients used in masks) or [R, G, B] arrays (for colored gradients). color_2 : tuple or list, optional Color for the second point in the gradient. As default, white. Either floats between 0 and 1 (for gradients used in masks) or [R, G, B] arrays (for colored gradients). shape : str, optional Shape of the gradient. Can be either ``"linear"``, ``"bilinear"`` or ``"circular"``. In a linear gradient the color varies in one direction, from point ``p1`` to point ``p2``. In a bilinear gradient it also varies symmetrically from ``p1`` in the other direction. In a circular gradient it goes from ``color_1`` to ``color_2`` in all directions. radius : float, optional If ``shape="radial"``, the radius of the gradient is defined with the parameter ``radius``, in pixels. offset : float, optional Real number between 0 and 1 indicating the fraction of the vector at which the gradient actually starts. For instance if ``offset`` is 0.9 in a gradient going from p1 to p2, then the gradient will only occur near p2 (before that everything is of color ``color_1``) If the offset is 0.9 in a radial gradient, the gradient will occur in the region located between 90% and 100% of the radius, this creates a blurry disc of radius ``d(p1, p2)``. Returns ------- image An Numpy array of dimensions (width, height, n_colors) of type float representing the image of the gradient. Examples -------- >>> color_gradient((10, 1), (0, 0), p2=(10, 0)) # from white to black [[1. 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1]] >>> >>> color_gradient( # from red to green ... (10, 1), # size ... (0, 0), # p1 ... p2=(10, 0), ... color_1=(255, 0, 0), # red ... color_2=(0, 255, 0), # green ... ) [[[ 0. 255. 0. ] [ 25.5 229.5 0. ] [ 51. 204. 0. ] [ 76.5 178.5 0. ] [102. 153. 0. ] [127.5 127.5 0. ] [153. 102. 0. ] [178.5 76.5 0. ] [204. 51. 0. ] [229.5 25.5 0. ]]] """ # np-arrayize and change x,y coordinates to y,x w, h = size color_1 = np.array(color_1).astype(float) color_2 = np.array(color_2).astype(float) if shape == "bilinear": if vector is None: if p2 is None: raise ValueError("You must provide either 'p2' or 'vector'") vector = np.array(p2) - np.array(p1) m1, m2 = [ color_gradient( size, p1, vector=v, color_1=1.0, color_2=0.0, shape="linear", offset=offset, ) for v in [vector, [-v for v in vector]] ] arr = np.maximum(m1, m2) if color_1.size > 1: arr = np.dstack(3 * [arr]) return arr * color_1 + (1 - arr) * color_2 p1 = np.array(p1[::-1]).astype(float) M = np.dstack(np.meshgrid(range(w), range(h))[::-1]).astype(float) if shape == "linear": if vector is None: if p2 is not None: vector = np.array(p2[::-1]) - p1 else: raise ValueError("You must provide either 'p2' or 'vector'") else: vector = np.array(vector[::-1]) norm = np.linalg.norm(vector) n_vec = vector / norm**2 # norm 1/norm(vector) p1 = p1 + offset * vector arr = (M - p1).dot(n_vec) / (1 - offset) arr = np.minimum(1, np.maximum(0, arr)) if color_1.size > 1: arr = np.dstack(3 * [arr]) return arr * color_1 + (1 - arr) * color_2 elif shape == "radial": if (radius or 0) == 0: arr = np.ones((h, w)) else: arr = (np.sqrt(((M - p1) ** 2).sum(axis=2))) - offset * radius arr = arr / ((1 - offset) * radius) arr = np.minimum(1.0, np.maximum(0, arr)) if color_1.size > 1: arr = np.dstack(3 * [arr]) return (1 - arr) * color_1 + arr * color_2 raise ValueError("Invalid shape, should be either 'radial', 'linear' or 'bilinear'")
[docs]def color_split( size, x=None, y=None, p1=None, p2=None, vector=None, color_1=0, color_2=1.0, gradient_width=0, ): """Make an image split in 2 colored regions. Returns an array of size ``size`` divided in two regions called 1 and 2 in what follows, and which will have colors color_1 and color_2 respectively. Parameters ---------- x : int, optional If provided, the image is split horizontally in x, the left region being region 1. y : int, optional If provided, the image is split vertically in y, the top region being region 1. p1, p2: tuple or list, optional Positions (x1, y1), (x2, y2) in pixels, where the numbers can be floats. Region 1 is defined as the whole region on the left when going from ``p1`` to ``p2``. p1, vector: tuple or list, optional ``p1`` is (x1,y1) and vector (v1,v2), where the numbers can be floats. Region 1 is then the region on the left when starting in position ``p1`` and going in the direction given by ``vector``. gradient_width : float, optional If not zero, the split is not sharp, but gradual over a region of width ``gradient_width`` (in pixels). This is preferable in many situations (for instance for antialiasing). Examples -------- >>> size = [200, 200] >>> >>> # an image with all pixels with x<50 =0, the others =1 >>> color_split(size, x=50, color_1=0, color_2=1) >>> >>> # an image with all pixels with y<50 red, the others green >>> color_split(size, x=50, color_1=[255, 0, 0], color_2=[0, 255, 0]) >>> >>> # An image split along an arbitrary line (see below) >>> color_split(size, p1=[20, 50], p2=[25, 70] color_1=0, color_2=1) """ if gradient_width or ((x is None) and (y is None)): if p2 is not None: vector = np.array(p2) - np.array(p1) elif x is not None: vector = np.array([0, -1.0]) p1 = np.array([x, 0]) elif y is not None: vector = np.array([1.0, 0.0]) p1 = np.array([0, y]) x, y = vector vector = np.array([y, -x]).astype("float") norm = np.linalg.norm(vector) vector = max(0.1, gradient_width) * vector / norm return color_gradient( size, p1, vector=vector, color_1=color_1, color_2=color_2, shape="linear" ) else: w, h = size shape = (h, w) if np.isscalar(color_1) else (h, w, len(color_1)) arr = np.zeros(shape) if x: arr[:, :x] = color_1 arr[:, x:] = color_2 elif y: arr[:y] = color_1 arr[y:] = color_2 return arr
[docs]def circle(screensize, center, radius, color=1.0, bg_color=0, blur=1): """Draw an image with a circle. Draws a circle of color ``color``, on a background of color ``bg_color``, on a screen of size ``screensize`` at the position ``center=(x, y)``, with a radius ``radius`` but slightly blurred on the border by ``blur`` pixels. Parameters ---------- screensize : tuple or list Size of the canvas. center : tuple or list Center of the circle. radius : float Radius of the circle, in pixels. bg_color : tuple or float, optional Color for the background of the canvas. As default, black. blur : float, optional Blur for the border of the circle. Examples -------- >>> from moviepy.video.tools.drawing import circle >>> >>> circle( ... (5, 5), # size ... (2, 2), # center ... 2, # radius ... ) array([[0. , 0. , 0. , 0. , 0. ], [0. , 0.58578644, 1. , 0.58578644, 0. ], [0. , 1. , 1. , 1. , 0. ], [0. , 0.58578644, 1. , 0.58578644, 0. ], [0. , 0. , 0. , 0. , 0. ]]) """ offset = 1.0 * (radius - blur) / radius if radius else 0 return color_gradient( screensize, p1=center, radius=radius, color_1=color, color_2=bg_color, shape="radial", offset=offset, )