def fill( self, ctx: cairo.Context, rng: Generator, x1: float, y1: float, x2: float, y2: float, ): with source(ctx, self.stops[-1].to_pattern()): ctx.fill_preserve() # Orient the canvas so that our gradient goes straight in direction of +Y. gradient_angle = angle((x1, y1), (x2, y2)) - (pi / 2) with translation(ctx, x1, y1), rotation(ctx, gradient_angle), source( ctx, self.stops[0].to_pattern()): # We translated and rotated the canvas, so our gradient control # vector is now from (0, 0) straight up: grad_control_y = distance((x1, y1), (x2, y2)) # Get a bounding box of what needs to be filled: start_x, start_y, end_x, end_y = ctx.fill_extents() ctx.new_path() for cx, cy, cr in self.pattern.func( rng, (start_x, start_y), (end_x, end_y), (0, 0), (0, grad_control_y), self.dot_radius, ): ctx.arc(cx, cy, cr, 0, tau) ctx.fill()
def draw(self, ctx: cairo.Context, relative_to=(0, 0)): x, y = self.pos - relative_to pat = self.color.to_pattern(x, y, self.size) with source(ctx, pat): ctx.arc(x, y, self.size, 0, math.tau) ctx.fill()
def draw_background(ctx: cairo.Context, width: int, height: int): # TODO: Add noise/cracks/stains bg_color = RadialGradient([Color(0.84, 0.81, 0.74), Color(0.55, 0.50, 0.36)]) center_x = width / 2 center_y = height / 2 with source(ctx, bg_color.to_pattern(center_x, center_y, center_x)), operator( ctx, cairo.Operator.DARKEN ): ctx.paint()
def triangle(self, color: Color): """ Draws a single triangle with the point down. """ with cairoctx.source(self.ctx, color.to_pattern()): self.ctx.rel_line_to(-self.scale / 2, -self.diag_offset) self.ctx.rel_line_to(self.scale, 0) self.ctx.rel_line_to(-self.scale / 2, self.diag_offset) self.ctx.fill()
def rectangle(self, color: Color): """ Draws a single rectangle starting at the bottom left. """ with cairoctx.source(self.ctx, color.to_pattern()): self.ctx.rel_line_to(0, -self.rect_height) self.ctx.rel_line_to(self.scale, 0) self.ctx.rel_line_to(0, -self.rect_height) self.ctx.rel_line_to(-self.scale, 0) self.ctx.fill()
def draw(self, ctx: cairo.Context, eye_radius: float, relative_to=(0, 0)): pos = self.pos - relative_to top_intersection = pos + np.array([0, self.opening / 2]) bottom_intersection = pos - np.array([0, self.opening / 2]) right_point = pos + np.array([self.size, 0]) left_point = pos - np.array([self.size, 0]) center_1, rad_1 = circle_from_3_points(left_point, top_intersection, right_point) center_2, rad_2 = circle_from_3_points(left_point, bottom_intersection, right_point) # Eyelid 1: ctx.push_group() with source(ctx, self.color.to_pattern()): # Restrict drawing area to eyeball: ctx.arc(pos[0], pos[1], eye_radius + 1, 0, math.tau) ctx.clip() ctx.paint() # Sub circle 1: with operator(ctx, cairo.Operator.CLEAR): ctx.arc(center_1[0], center_1[1], rad_1, 0, math.tau) ctx.fill() ctx.reset_clip() with source(ctx, ctx.pop_group()): ctx.paint() # Eyelid 2: ctx.push_group() with source(ctx, self.color.to_pattern()): # Restrict drawing area to eyeball: ctx.arc(pos[0], pos[1], eye_radius + 1, 0, math.tau) ctx.clip() ctx.paint() # Sub circle 2: with operator(ctx, cairo.Operator.CLEAR): ctx.arc(center_2[0], center_2[1], rad_2, 0, math.tau) ctx.fill() ctx.reset_clip() with source(ctx, ctx.pop_group()): ctx.paint()
def finalize(self, sim: Simulation): for id, p in self.trails.items(): props = self.particle_props[id] if self.line_width is LineWidth.MASS: lw = log(props.mass) + 0.1 self.ctx.set_line_width(lw) elif self.line_width is LineWidth.CHARGE: lw = log(abs(props.total_charge)) self.ctx.set_line_width(lw) elif self.line_width is LineWidth.DEPTH: lw = 2.0 + 0.01 * props.position[2] self.ctx.set_line_width(lw) if self.color_scheme is ColorScheme.BW: color = Color(0.0, 0.0, 0.0) else: color = self.color_scheme.gen_color(self.rng, props) self.ctx.move_to(*p[0]) for control_point, destination in zip(p[1::2], p[2::2]): self.ctx.curve_to(*control_point, *control_point, *destination) with cairoctx.source(self.ctx, color.to_pattern()): self.ctx.stroke() if self.color_scheme is ColorScheme.COMIC: # Overlay a lightening radial gradient to create a "bang". grad = RadialGradient((Color(1.0, 1.0, 1.0, 0.9), Color(1.0, 1.0, 1.0, 0.0))) mid_x = self.width / 2.0 mid_y = self.height / 2.0 radius = min(self.width, self.height) / 4.0 with cairoctx.operator(self.ctx, cairo.Operator.ATOP), cairoctx.source( self.ctx, grad.to_pattern(mid_x, mid_y, radius)): self.ctx.arc(mid_x, mid_y, radius, 0.0, pi * 2) self.ctx.fill()
def draw(self, ctx: cairo.Context): with translation(ctx, self.pos[0], self.pos[1]): with rotation(ctx, self.rotation): with source(ctx, self.color.to_pattern()): ctx.arc(0, 0, self.size, 0, math.tau) ctx.fill() if self.iris: self.iris.draw(ctx, relative_to=self.pos) self.pupil.draw(ctx, relative_to=self.pos) if self.eyelids: self.eyelids.draw(ctx, self.size, relative_to=self.pos)
def draw_moon( ctx: cairo.Context, pos_x: float, pos_y: float, radius: float, eclipse_pct: float = -1.0, ): moon_color = Color(0.9, 0.9, 0.9) ctx.arc(pos_x, pos_y, radius, 0, tau) ctx.stroke_preserve() with source( ctx, moon_color.to_pattern(), ): ctx.fill_preserve() ctx.clip() with source(ctx, Color(0.0, 0.0, 0.0).to_pattern()): eclipse_cx = pos_x + eclipse_pct * 2.0 * radius ctx.arc(eclipse_cx, pos_y, radius, 0, tau) ctx.fill() ctx.reset_clip()
def add_grid(self, width: float, height: float, rows: int, cols: int): rowheight = height // rows colwidth = width // cols with cairoctx.source(self.ctx, Color(0.5, 0.5, 0.5).to_pattern()): for col in range(1, cols + 1): x = col * colwidth self.ctx.move_to(x, 0) self.ctx.line_to(x, height) self.ctx.stroke() for row in range(1, rows + 1): y = row * rowheight self.ctx.move_to(0, y) self.ctx.line_to(width, y) self.ctx.stroke()
def draw_grid(ctx: cairo.Context, width: float, height: float, rows: int, cols: int): rowheight = height // rows colwidth = width // cols with cairoctx.source(ctx, Color(0.5, 0.5, 0.5).to_pattern()): for col in range(1, cols + 1): x = col * colwidth ctx.move_to(x, 0) ctx.line_to(x, height) ctx.stroke() for row in range(1, rows + 1): y = row * rowheight ctx.move_to(0, y) ctx.line_to(width, y) ctx.stroke()
def draw_star(ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, size: float): spikes = rng.integers(5, 8) outer_points = list( jitter_points(points_along_arc(pos_x, pos_y, size, 0, tau, spikes), rng, 0.1 * size), ) inner_offset = pi / spikes inner_points = list( jitter_points( points_along_arc(pos_x, pos_y, size / 2.0, inner_offset, inner_offset + tau, spikes), rng, 0.1 * size, )) ctx.move_to(*inner_points[-1]) for (ax, ay), (bx, by) in zip(outer_points, inner_points): ctx.line_to(ax, ay) ctx.line_to(bx, by) ctx.stroke_preserve() with source(ctx, Color(1.0, 1.0, 1.0).to_pattern()): ctx.fill()
def draw(self, ctx: cairo.Context): with source(ctx, self.color.to_pattern()): ctx.paint()