def draw_markers(self): # The paint of markers in between white keys white_white_paint = skia.Paint( AntiAlias=True, Color=skia.Color4f( *self.global_colors["marker_color_between_two_white"], 1), Style=skia.Paint.kStroke_Style, StrokeWidth=1, ) current_center = 0 # Check if prev and this key is white, keeps track of a current_center, create rect to draw and draw for index, key_index in enumerate(self.piano_keys.keys()): key = self.piano_keys[key_index] if not index == 0: prevkey = self.piano_keys[key_index - 1] if (prevkey.is_white) and (key.is_white): current_center += self.tone_width rect = skia.Rect(current_center - self.semitone_width, 0, current_center - self.semitone_width, self.mmvskia_main.context.height) self.mmvskia_main.skia.canvas.drawRect( rect, white_white_paint) else: current_center += self.semitone_width # Ask each key to draw their CENTERED marker for key_index in self.piano_keys.keys(): self.piano_keys[key_index].draw_marker()
def draw_markers(self): c = 0.35 white_white_color = skia.Color4f(c, c, c, 1) white_white_paint = skia.Paint( AntiAlias=True, Color=white_white_color, Style=skia.Paint.kStroke_Style, StrokeWidth=1, ) current_center = 0 for index, key_index in enumerate(self.piano_keys.keys()): key = self.piano_keys[key_index] if not index == 0: prevkey = self.piano_keys[key_index - 1] if (prevkey.is_white) and (key.is_white): current_center += self.tone_width rect = skia.Rect(current_center - self.semitone_width, 0, current_center + 1 - self.semitone_width, self.vectorial.context.height) self.skia.canvas.drawRect(rect, white_white_paint) else: current_center += self.semitone_width for key_index in self.piano_keys.keys(): self.piano_keys[key_index].draw_marker()
def get_image(self): #=================== # Draw image to fit tile set's pixel rectangle surface = skia.Surface(int(self.__scaling[0]*self.__size[0] + 0.5), int(self.__scaling[1]*self.__size[1] + 0.5)) canvas = surface.getCanvas() canvas.clear(skia.Color4f(0xFFFFFFFF)) self.__svg_drawing.draw_element(canvas, self.__bbox) log('Making image snapshot...') image = surface.makeImageSnapshot() return image.toarray(colorType=skia.kBGRA_8888_ColorType)
def get_tile(self, tile): #======================== surface = skia.Surface(*self.__tile_size) ## In pixels... canvas = surface.getCanvas() canvas.clear(skia.Color4f(0xFFFFFFFF)) canvas.translate(self.__pixel_offset[0] + (self.__tile_origin[0] - tile.x)*self.__tile_size[0], self.__pixel_offset[1] + (self.__tile_origin[1] - tile.y)*self.__tile_size[1]) quadkey = mercantile.quadkey(tile) self.__svg_drawing.draw_element(canvas, self.__tile_bboxes.get(quadkey)) image = surface.makeImageSnapshot() return image.toarray(colorType=skia.kBGRA_8888_ColorType)
def configure_color(self): # Marker color on sharp keys self.marker_color = skia.Color4f( *self.piano_roll.global_colors["marker_color_sharp_keys"], 0.1) # Marker color between two white keys self.marker_color_subtle_brighter = skia.Color4f( *self.piano_roll.global_colors["marker_color_between_two_white"], 0.3) # Note is a sharp key, black idle, gray on press if "#" in self.name: self.color_active = skia.Color4f( *self.piano_roll.global_colors["sharp_key_pressed"], 1) self.color_idle = skia.Color4f( *self.piano_roll.global_colors["sharp_key_idle"], 1) self.is_white = False self.is_black = True # Note is plain key, white on idle, graw on press else: self.color_active = skia.Color4f( *self.piano_roll.global_colors["plain_key_pressed"], 1) self.color_idle = skia.Color4f( *self.piano_roll.global_colors["plain_key_idle"], 1) self.is_white = True self.is_black = False
def parse_colors_dict(self, colors_dict): for key, value in colors_dict.items(): if value is None: colors_dict[key] = None continue if isinstance(value, dict): colors_dict[key] = self.parse_colors_dict(colors_dict = value) continue if isinstance(value[0], int): value = [v / 256 for v in value] colors_dict[key] = skia.Color4f(*value) return colors_dict
def particles(self): print(f"Saving generated particle N=[", end="", flush=True) # Repeat for N images for i in range(self.n_images): # Reset canvas to transparent self.skia.reset_canvas() # How much nodes of gradients in this particle npoints = random.randint(1, 6) # Random scalars for each node, plus ending at zero scalars = [random.uniform(0.2, 1) for _ in range(npoints)] + [0] # Colors list colors = [] # Iterate in decreasing order for scalar in reversed(sorted(scalars)): colors.append(skia.Color4f(1, 1, 1, scalar)) # Create the skia paint with a circle at the center that ends on the edge paint = skia.Paint(Shader=skia.GradientShader.MakeRadial( center=(self.width / 2, self.height / 2), radius=self.width / 2, colors=colors, )) # Draw the particles self.canvas.drawPaint(paint) # # Save the image # Get the PIL image from the array of the canvas img = Image.fromarray(self.skia.canvas_array()) # Pretty print if not i == self.n_images - 1: print(f"{i}, ", end="", flush=True) else: print(f"{i}]") # Save the particle to the output dir img.save(self.output_dir + f"/particle-{i}.png", quality=100)
def draw(self, notes_playing): if self.is_black: away = (self.height * (0.33)) else: away = 0 coords = [ self.center_x - (self.width / 2), self.resolution_height - self.height, self.center_x + (self.width / 2), self.resolution_height - away, ] self.active = self.note in notes_playing if self.active: color = self.color_active else: color = self.color_idle # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kFill_Style, StrokeWidth=2, ) border = skia.Paint( AntiAlias=True, Color=skia.Color4f(0, 0, 0, 1), Style=skia.Paint.kStroke_Style, StrokeWidth=2, ) # Rectangle border rect = skia.Rect(*coords) # Draw the border self.skia.canvas.drawRect(rect, paint) self.skia.canvas.drawRect(rect, border)
def draw(self, notes_playing): # If the note is black we leave a not filled spot on the bottom, this is the ratio of it away = (self.height * (0.33)) if self.is_black else 0 # Based on the away, center, width, height etc, get the coords of this piano key coords = [ self.center_x - (self.width / 2), self.resolution_height - self.height, self.center_x + (self.width / 2), self.resolution_height - away, ] # Is the note active self.active = self.note in notes_playing # Get the color based on if the note is active or not color = self.color_active if self.active else self.color_idle # Make the skia Paint and key_paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kFill_Style, StrokeWidth=2, ) # The border of the key key_border = skia.Paint( AntiAlias=True, Color=skia.Color4f(0, 0, 0, 1), Style=skia.Paint.kStroke_Style, StrokeWidth=2, ) # Rectangle border rect = skia.Rect(*coords) # Draw the border self.mmvskia_main.skia.canvas.drawRect(rect, key_paint) self.mmvskia_main.skia.canvas.drawRect(rect, key_border)
def configure_color(self): c = min(self.background_color + (40 / 255), 1) self.marker_color = skia.Color4f(c, c, c, 0.1) c = min(self.background_color + (60 / 255), 1) self.marker_color_subtle_brighter = skia.Color4f(c, c, c, 0.3) # Note is a sharp key, black idle, gray on press if "#" in self.name: self.color_active = skia.Color4f(0, 0, 0, 1) self.color_idle = skia.Color4f(0.2, 0.2, 0.2, 1) self.is_white = False self.is_black = True else: self.color_active = skia.Color4f(0.7, 0.7, 0.7, 1) self.color_idle = skia.Color4f(1, 1, 1, 1) self.is_white = True self.is_black = False
def build(self, fitted_ffts: dict, frequencies: list, config: dict, effects): debug_prefix = "[MMVSkiaMusicBarsCircle.build]" # We first store the coordinates to draw and their respective paints, draw afterwards data = {} # # TODO: This code was originally set to have "linear" or "symmetric" modes, I think # # we should keep symmetric mode only, that's my opinion # Loop on both channels for channel in (["l", "r"]): # Init blank channel of list of coordinates and paints data[channel] = { "coordinates": [], "paints": [], } # The FFT of this channel from the dictionary this_channel_fft = fitted_ffts[channel] # Length of the FFT NFFT = len(this_channel_fft) # For each magnite of the FFT and their respective index for index, magnitude in enumerate(this_channel_fft): # This is symmetric, so half a rotation divided by how much bars # Remember we're in Radians, pi radians is 180 degrees (half an rotation) angle_between_two_bars = math.pi / NFFT # We have to start from half a distance between bars and end on a full rotation minus a bars distance # depending on the channel we're in if channel == "l": theta = self.mmv.functions.value_on_line_of_two_points( Xa=0, Ya=(math.pi / 2) + (angle_between_two_bars / 2), Xb=NFFT, Yb=(math.pi / 2) + math.pi + (angle_between_two_bars / 2), get_x=index, ) elif channel == "r": theta = self.mmv.functions.value_on_line_of_two_points( Xa=0, Ya=(math.pi / 2) - (angle_between_two_bars / 2), Xb=NFFT, Yb=(math.pi / 2) - math.pi - (angle_between_two_bars / 2), get_x=index, ) # Scale the magnitude according to the resolution magnitude *= self.mmv.context.resolution_ratio_multiplier # Calculate our flatten scalar as explained previously flatten_scalar = self.mmv.functions.value_on_line_of_two_points( Xa=20, Ya=self.fft_20hz_multiplier, Xb=20000, Yb=self.fft_20khz_multiplier, get_x=frequencies[0][index]) # The actual size of the music bar size = (magnitude) * (flatten_scalar) * ( self.bar_magnitude_multiplier) # Hard crop maximum limit if size > self.maximum_bar_size: size = self.maximum_bar_size # We send an r, theta just in case we want to do something with it later on data[channel]["coordinates"].append([ (self.minimum_bar_size + size) * effects["size"], theta, ]) # If a bar has a high magnitude, set the stroke width to be higher if self.bigger_bars_on_magnitude: # Calculate how much to add bigger_bars_on_magnitude = ( magnitude / self.bigger_bars_on_magnitude_add_magnitude_divided_by) # Scale according to the resolution bigger_bars_on_magnitude /= self.mmv.context.resolution_ratio_multiplier else: bigger_bars_on_magnitude = 0 # # Coloring # Radial colors if self.color_preset == "colorful": # Rotate the colors a bit on each step color_shift_on_angle = theta if self.color_rotates: color_shift_on_angle += (self.mmv.core.this_step / self.color_rotate_speed) # Define the color of the bars colors = [ abs(math.sin((color_shift_on_angle / 2))), abs( math.sin((color_shift_on_angle + ((1 / 3) * 2 * math.pi)) / 2)), abs( math.sin((color_shift_on_angle + ((2 / 3) * 2 * math.pi)) / 2)), ] + [0.89] # not full opacity # Make a skia color with the colors list as argument color = skia.Color4f(*colors) # Make the skia Paint and this_bar_paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=8 * self.mmv.context.resolution_ratio_multiplier + bigger_bars_on_magnitude, ) if self.color_preset == "white": # Define the color of the bars colors = [1.0, 1.0, 1.0, 0.89] # Make a skia color with the colors list as argument color = skia.Color4f(*colors) # Make the skia Paint and this_bar_paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=8 * self.mmv.context.resolution_ratio_multiplier + bigger_bars_on_magnitude, ) # Store it on a list do draw in the end data[channel]["paints"].append(this_bar_paint) # Our list of coordinates and paints, invert the right channel for drawing the path in the right direction # Not reversing it will yield "symmetric" bars along the diagonal coordinates = data["l"]["coordinates"] + [ x for x in reversed(data["r"]["coordinates"]) ] paints = data["l"]["paints"] + [ x for x in reversed(data["r"]["paints"]) ] # # # # # # # # # # # # These two code blocks are deprecated, not sure if they'll be used in a config # # # # # # # # # # # # # Filled background if False: # self.config["draw_background"] path = skia.Path() white_background = skia.Paint( AntiAlias=True, Color=skia.ColorWHITE, Style=skia.Paint.kFill_Style, StrokeWidth=3, ImageFilter=skia.ImageFilters.DropShadow( 3, 3, 5, 5, skia.ColorBLACK), MaskFilter=skia.MaskFilter.MakeBlur(skia.kNormal_BlurStyle, 1.0)) more = 1.05 self.polar.from_r_theta(coordinates[0][0] * more, coordinates[0][1]) polar_offset = self.polar.get_rectangular_coordinates() path.moveTo( (self.center_x + polar_offset[0]), (self.center_y + polar_offset[1]), ) for coord_index, coord in enumerate(coordinates): # TODO: implement this function in DataUtils for not repeating myself get_nearby = 4 size_coordinates = len(coordinates) real_state = coordinates * 3 nearby_coordinates = real_state[size_coordinates + (coord_index - get_nearby):size_coordinates + (coord_index + get_nearby)] # [0, 1, 2, 3, 4] --> weights= # 3 4 5, 4, 3 n = len(nearby_coordinates) weights = [n - abs((n / 2) - x) for x in range(n)] s = 0 for index, item in enumerate(nearby_coordinates): s += item[0] * weights[index] avg_coord = s / sum(weights) self.polar.from_r_theta(avg_coord * more, coord[1]) polar_offset = self.polar.get_rectangular_coordinates() path.lineTo( (self.center_x + polar_offset[0]), (self.center_y + polar_offset[1]), ) self.mmv.skia.canvas.drawPath(path, white_background) # Countour, stroke if False: # self.config["draw_black_border"] more = 2 path = skia.Path() black_stroke = skia.Paint(AntiAlias=True, Color=skia.ColorWHITE, Style=skia.Paint.kStroke_Style, StrokeWidth=6, ImageFilter=skia.ImageFilters.DropShadow( 3, 3, 5, 5, skia.ColorWHITE), MaskFilter=skia.MaskFilter.MakeBlur( skia.kNormal_BlurStyle, 1.0)) for coord_index, coord in enumerate(coordinates): get_nearby = 10 size_coordinates = len(coordinates) real_state = coordinates * 3 nearby_coordinates = real_state[size_coordinates + (coord_index - get_nearby):size_coordinates + (coord_index + get_nearby)] n = len(nearby_coordinates) weights = [n - abs((n / 2) - x) for x in range(n)] s = 0 for index, item in enumerate(nearby_coordinates): s += item[0] * weights[index] avg_coord = s / sum(weights) self.polar.from_r_theta( self.minimum_bar_size + ((avg_coord - self.minimum_bar_size) * more), coord[1]) polar_offset = self.polar.get_rectangular_coordinates() coords = [(self.center_x + polar_offset[0]), (self.center_y + polar_offset[1])] if coord_index == 0: path.moveTo(*coords) path.lineTo(*coords) self.mmv.skia.canvas.drawPath(path, black_stroke) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Draw the main bars according to their index and coordinate for index, coord in enumerate(coordinates): # Empty path for drawing the bars path = skia.Path() # Move to wanted center if self.bar_starts_from == "center": path.moveTo(self.center_x, self.center_y) # Move to the last bar end position elif self.bar_starts_from == "last": self.polar.from_r_theta(coordinates[index - 1][0], coordinates[index - 1][1]) polar_offset = self.polar.get_rectangular_coordinates() path.moveTo(self.center_x + polar_offset[0], self.center_y + polar_offset[1]) else: raise RuntimeError( debug_prefix, f"Invalid bar_starts_from: [{self.bar_starts_from}]") # Get a polar coordinate point, convert to rectangular self.polar.from_r_theta(coord[0], coord[1]) polar_offset = self.polar.get_rectangular_coordinates() # Move to the final desired point path.lineTo( self.center_x + polar_offset[0], self.center_y + polar_offset[1], ) # Draw the path according to its index's paint self.mmv.skia.canvas.drawPath(path, paints[index])
def build(self, config, effects): # Get "needed" variables total_steps = self.mmv.context.total_steps completion = self.functions.proportion( total_steps, 1, self.mmv.core.this_step) # Completion from 0-1 means resolution_ratio_multiplier = self.mmv.context.resolution_ratio_multiplier # We push the bar downwards according to the avg amplitude for a nice shaky-blur effect offset_by_amplitude = self.mmv.core.modulators[ "average_value"] * self.config[ "shake_scalar"] * resolution_ratio_multiplier if self.config["position"] == "top": offset_by_amplitude *= (-1) # White full opacity color colors = [1, 1, 1, 1] # Make a skia color with the colors list as argument color = skia.Color4f(*colors) # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=10 * resolution_ratio_multiplier, # + (magnitude/4), # ImageFilter=skia.ImageFilters.DropShadow(3, 3, 5, 5, skia.ColorWHITE), ) # The direction we're walking centered at origin, $\vec{AB} = A - B$ path_vector = np.array( [self.end_x - self.start_x, self.end_y - self.start_y]) # Proportion we already walked path_vector = path_vector * completion # Draw the main line starting at the start coordinates, push down by offset_by_amplitude path = skia.Path() path.moveTo(self.start_x, self.start_y + offset_by_amplitude) path.lineTo(self.start_x + path_vector[0], self.start_y + path_vector[1] + offset_by_amplitude) self.mmv.skia.canvas.drawPath(path, paint) # Borders around image if True: # Distance away from s distance = 9 * resolution_ratio_multiplier colors = [1, 1, 1, 0.7] # Make a skia color with the colors list as argument color = skia.Color4f(*colors) # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=2, ) # Rectangle border border = skia.Rect(self.start_x - distance, self.start_y - distance + offset_by_amplitude, self.end_x + distance, self.end_y + distance + offset_by_amplitude) # Draw the border self.mmv.skia.canvas.drawRect(border, paint)
def polygons(self): print(f"Saving generated polygons background N=[", end="", flush=True) BASE_GRADIENT_LINEAR = True MUTATION = True LOW_POLY = True RANDOM = True GREYSCALE = False for i in range(self.n_images): if BASE_GRADIENT_LINEAR: if RANDOM: color1 = skia.Color4f(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1), 1) color2 = skia.Color4f(random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1), 1) if GREYSCALE: gradient1, gradient2 = random.uniform(0, 1), random.uniform( 0, 1) color1 = skia.Color4f(gradient1, gradient1, gradient1, 1) color2 = skia.Color4f(gradient2, gradient2, gradient2, 1) paint = skia.Paint(Shader=skia.GradientShader.MakeLinear( points=[(random.randint(-self.width / 2, 0), random.randint(-self.height / 2, 0)), (self.width + random.randint(0, self.width / 2), self.height + random.randint(0, self.height / 2))], colors=[color1, color2])) self.canvas.drawPaint(paint) colors = self.skia.canvas_array() if LOW_POLY: break_x = 20 break_y = 15 rectangle_widths = np.linspace(-self.width / 4, 1.25 * self.width, num=break_x) rectangle_heights = np.linspace(-self.height / 4, 1.25 * self.height, num=break_y) rectangle_width = self.width / break_x rectangle_height = self.height / break_y xx, yy = np.meshgrid(rectangle_widths, rectangle_heights) pairs = list(np.dstack([xx, yy]).reshape(-1, 2)) if MUTATION: for index in range(len(pairs)): pairs[index][0] = pairs[index][0] + random.uniform( 0, rectangle_width) pairs[index][1] = pairs[index][1] + random.uniform( 0, rectangle_height) # print(pairs) rectangle_points = [] for index, point in enumerate(list(pairs)): samerow = not ((index + 1) % break_x == 0) # print(pairs[index], index, samerow) try: # If they are on the same height if samerow: rectangle_points.append([ list(pairs[index]), list(pairs[index + break_x]), list(pairs[index + break_x + 1]), list(pairs[index + 1]), ]) except IndexError: pass for rectangle in rectangle_points: average_x = int(sum([val[0] for val in rectangle]) / 4) average_y = int(sum([val[1] for val in rectangle]) / 4) # print(rectangle, average_x, average_y) rectangle_color = colors[min(max(average_y, 0), self.height - 1)][min( max(average_x, 0), self.width - 1)] rectangle_color_fill = [x / 255 for x in rectangle_color] rectangle_color_border = [ x / 255 - 0.05 for x in rectangle_color ] # print(rectangle_color) # Make a skia color with the colors list as argument color = skia.Color4f(*rectangle_color_fill) # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kFill_Style, StrokeWidth=2, ) color = skia.Color4f(*rectangle_color_border) border = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=1, # ImageFilter=skia.ImageFilters.DropShadow(3, 3, 5, 5, color) ) path = skia.Path() path.moveTo(*rectangle[0]) rectangle.append(rectangle[0]) for point in rectangle: path.lineTo(*point) self.canvas.drawPath(path, paint) self.canvas.drawPath(path, border) # Save img = self.skia.canvas_array() # Pretty print if not i == self.n_images - 1: print(f"{i}, ", end="", flush=True) else: print(f"{i}]") img = Image.fromarray(img).convert('RGB') img.save(self.output_dir + f"/img{i}.jpg", quality=100)
def next(self, depth=LOG_NO_DEPTH) -> None: debug_prefix = "[MMVSkiaImage.next]" ndepth = depth + LOG_NEXT_DEPTH # Next step self.current_step += 1 if self.preludec["next"]["log_current_step"]: logging.debug( f"{ndepth}{debug_prefix} [{self.identifier}] Next step, current step = [{self.current_step}]" ) # Animation has ended, this current_animation isn't present on path.keys if self.current_animation not in list(self.animation.keys()): self.is_deletable = True # Log we are marked to be deleted if self.preludec["next"]["log_became_deletable"]: logging.debug( f"{ndepth}{debug_prefix} [{self.identifier}] Object is out of animation, marking to be deleted" ) return # The animation we're currently playing this_animation = self.animation[self.current_animation] animation = this_animation["animation"] steps = animation[ "steps"] * self.mmvskia_main.context.fps_ratio_multiplier # Scale the interpolations # The current step is one above the steps we've been told, next animation if self.current_step >= steps + 1: self.current_animation += 1 self.current_step = 0 return # Reset offset, pending self.offset = [0, 0] self.image.pending = {} sg = time.time() self.image.reset_to_original_image() self._reset_effects_variables() position = this_animation["position"] path = position["path"] if "modules" in this_animation: modules = this_animation["modules"] self.is_vectorial = "vectorial" in modules # The video module must be before everything as it gets the new frame if "video" in modules: s = time.time() this_module = modules["video"] # We haven't set a video capture or it has ended if self.video is None: self.video = cv2.VideoCapture(this_module["path"]) # Can we read next frame? if not, go back to frame 0 for a loop ok, frame = self.video.read() if not ok: # cry self.video.set(cv2.CAP_PROP_POS_FRAMES, 0) ok, frame = self.video.read() # CV2 utilizes BGR matrix, but we need RGB frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) self.image.load_from_array(frame) self.image.resize_to_resolution(width=this_module["width"], height=this_module["height"], override=True) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Video module .next() took [{time.time() - s:.010f}]" ) if "rotate" in modules: s = time.time() this_module = modules["rotate"] rotate = this_module["object"] amount = rotate.next() amount = round(amount, self.ROUND) if not self.is_vectorial: self.image.rotate(amount, from_current_frame=True) else: self.rotate_value = amount if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Rotate module .next() took [{time.time() - s:.010f}]" ) if "resize" in modules: s = time.time() this_module = modules["resize"] resize = this_module["object"] # Where the vignetting intensity is pointing to according to our resize.next() self.size = resize.get_value() if not self.is_vectorial: # If we're going to rotate, resize the rotated frame which is not the original image offset = self.image.resize_by_ratio( self.size, from_current_frame=True) if this_module["keep_center"]: self.offset[0] += offset[0] self.offset[1] += offset[1] if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Resize module .next() took [{time.time() - s:.010f}]" ) # DONE if "blur" in modules: s = time.time() this_module = modules["blur"] blur = this_module["object"] blur.next() amount = blur.get_value() self.image_filters.append( skia.ImageFilters.Blur(amount, amount)) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Blur module .next() took [{time.time() - s:.010f}]" ) if "fade" in modules: s = time.time() this_module = modules["fade"] fade = this_module["object"] fade.next() self.image.transparency(fade.get_value()) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Fade module .next() took [{time.time() - s:.010f}]" ) # Apply vignetting if "vignetting" in modules: s = time.time() this_module = modules["vignetting"] vignetting = this_module["object"] # Where the vignetting intensity is pointing to according to our vignetting.next() vignetting.get_center() next_vignetting = vignetting.get_value() # This is a somewhat fake vignetting, we just start a black point with full transparency # at the center and make a radial gradient that is black with no transparency at the radius self.mmvskia_main.skia.canvas.drawPaint({ 'Shader': skia.GradientShader.MakeRadial( center=(vignetting.center_x, vignetting.center_y), radius=next_vignetting, colors=[ skia.Color4f(0, 0, 0, 0), skia.Color4f(0, 0, 0, 1) ]) }) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Vignetting module .next() took [{time.time() - s:.010f}]" ) if "vectorial" in modules: s = time.time() this_module = modules["vectorial"] vectorial = this_module["object"] effects = { "size": self.size, "rotate": self.rotate_value, "image_filters": self.image_filters, } # Visualizer blit itself into the canvas automatically vectorial.next(effects) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Vectorial module .next() took [{time.time() - s:.010f}]" ) if self.preludec["next"]["debug_timings"]: logging.debug( f"{depth}{debug_prefix} [{self.identifier}] Global .next() took [{time.time() - sg:.010f}]" ) # Iterate through every position module for modifier in path: # # Override modules argument = [self.x, self.y] + self.offset # Move according to a Point (be stationary) if self.mmvskia_main.utils.is_matching_type( [modifier], [MMVSkiaModifierPoint]): # Attribute (x, y) to Point's x and y [self.x, self.y], self.offset = modifier.next(*argument) # Move according to a Line (interpolate current steps) if self.mmvskia_main.utils.is_matching_type([modifier], [MMVSkiaModifierLine]): # Interpolate and unpack next coordinate [self.x, self.y], self.offset = modifier.next(*argument) # # Offset modules # Get next shake offset value if self.mmvskia_main.utils.is_matching_type( [modifier], [MMVSkiaModifierShake]): [self.x, self.y], self.offset = modifier.next(*argument)
def skia(self): return skia.Color4f(self.r, self.g, self.b, self.a)
def test_Color4f_init(args): assert isinstance(skia.Color4f(*args), skia.Color4f)
def test_Color4f_getitem_setitem(): color4f = skia.Color4f(0xFFFFFFFF) assert isinstance(color4f[0], float) color4f[0] = 1.0
def run(self): # Calculate fps? if self.pyskt_context.show_debug: frame_times = [0] * 120 frame = 0 last_time_completed = time.time() # Link events to parsers glfw.set_window_size_callback(self.window, self.events.on_window_resize) glfw.set_mouse_button_callback(self.window, self.events.mouse_callback) glfw.set_scroll_callback(self.window, self.events.mouse_callback) glfw.set_key_callback(self.window, self.events.keyboard_callback) glfw.set_drop_callback(self.window, self.events.on_file_drop) scroll_text_x = self.pyskt_context.width // 2 scroll_text_y = self.pyskt_context.height // 2 wants_to_go = [scroll_text_x, scroll_text_y] threading.Thread(target=self.events_loop).start() # Loop until the user closes the window while not glfw.window_should_close(self.window): # Wait events if said to # if self.pyskt_context.wait_events: # Clear canvas self.canvas.clear(self.colors.background) # Get mouse position self.mouse_pos = glfw.get_cursor_pos(self.window) self.check_mouse_moved() if self.events.left_click: wants_to_go[0] -= self.events.scroll * 50 else: wants_to_go[1] -= self.events.scroll * 50 # We have now to recursively search through the components dictionary scroll_text_x = scroll_text_x + (wants_to_go[0] - scroll_text_x) * 0.3 scroll_text_y = scroll_text_y + (wants_to_go[1] - scroll_text_y) * 0.3 self.draw_utils.anchored_text( canvas=self.canvas, text="A Really long and centered text", x=scroll_text_x, y=scroll_text_y, anchor_x=0.5, anchor_y=0.5, ) # Hover testing for _ in range(20): random.seed(_) xywh_rect = [ random.randint(0, 1000), random.randint(0, 1000), random.randint(0, 400), random.randint(0, 400) ] # Rectangle border # rect = self.pyskt_processing.rectangle_x_y_w_h_to_coordinates(*xywh_rect) info = self.pyskt_processing.point_against_rectangle( self.mouse_pos, xywh_rect) if info["is_inside"]: paint = skia.Paint( AntiAlias=True, Color=skia.Color4f(1, 0, 1, 1), Style=skia.Paint.kFill_Style, StrokeWidth=2, ) else: c = 1 - (info["distance"] / 1080) paint = skia.Paint( AntiAlias=True, Color=skia.Color4f(c, c, c, 1), Style=skia.Paint.kFill_Style, StrokeWidth=2, ) # Draw the border self.canvas.drawRect( skia.Rect( *self.pyskt_processing.rectangle_x_y_w_h_to_skia_rect( *xywh_rect)), paint) # # # # Show fps if self.pyskt_context.show_debug: frame_times[frame % 120] = time.time() - last_time_completed absolute_frame_times = [x for x in frame_times if not x == 0] fps = 1 / (sum(absolute_frame_times) / len(absolute_frame_times)) last_time_completed = time.time() frame += 1 self.draw_utils.anchored_text( canvas=self.canvas, text=[f"{fps=:.1f}", f"mouse_pos={self.mouse_pos}"], x=0, y=0, anchor_x=0, anchor_y=0, # kwargs font=skia.Font(skia.Typeface('Arial'), 12), ) # If any event doesn't return to "None" state self.events.reset_non_ending_states() # Flush buffer self.surface.flushAndSubmit() # Swap front and back buffers glfw.swap_buffers(self.window) # Poll for and process events glfw.poll_events() # End glfw glfw.terminate()
def draw_note(self, velocity, start, end, channel, note, name): # Get the note colors for this channel, we receive a dict with "sharp" and "plain" keys note_colors = self.color_channels.get(channel, self.color_channels["default"]) # Is a sharp key if "#" in name: width = self.semitone_width * 0.9 color = skia.Color4f(*note_colors["sharp"], 1) # Plain key else: width = self.tone_width * 0.6 color = skia.Color4f(*note_colors["plain"], 1) # Make the skia Paint note_paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kFill_Style, # Shader=skia.GradientShader.MakeLinear( # points=[(0.0, 0.0), (self.mmvskia_main.context.width, self.mmvskia_main.context.height)], # colors=[skia.Color4f(0, 0, 1, 1), skia.Color4f(0, 1, 0, 1)]), StrokeWidth=2, ) # Border of the note note_border_paint = skia.Paint( AntiAlias=True, Color=skia.Color4f(*note_colors["border"], 1), Style=skia.Paint.kStroke_Style, # ImageFilter=skia.ImageFilters.DropShadow(3, 3, 5, 5, skia.ColorBLUE), # MaskFilter=skia.MaskFilter.MakeBlur(skia.kNormal_BlurStyle, 2.0), StrokeWidth=max( self.mmvskia_main.context.resolution_ratio_multiplier * 2, 1), ) # Horizontal we have it based on the tones and semitones we calculated previously # this is the CENTER of the note x = self.keys_centers[note] # The Y is a proportion of, if full seconds of midi content, it's the viewport height itself, # otherwise it's a proportion to the processing time according to a start value in seconds y = self.functions.proportion( self.config["seconds_of_midi_content"], self.viewport_height, #*2, self.mmvskia_main.context.current_time - start) # The height is just the proportion of, seconds of midi content is the maximum height # how much our key length (end - start) is according to that? height = self.functions.proportion( self.config["seconds_of_midi_content"], self.viewport_height, end - start) # Build the coordinates of the note # Note: We add and subtract half a width because X is the center # while we need to add from the viewport out heights on the Y coords = [ x - (width / 2), y + (self.viewport_height) - height, x + (width / 2), y + (self.viewport_height), ] # Rectangle border of the note rect = skia.Rect(*coords) # Draw the note and border self.mmvskia_main.skia.canvas.drawRect(rect, note_paint) self.mmvskia_main.skia.canvas.drawRect(rect, note_border_paint)
def __init__(self, pyskt_main, *args, **kwargs): self.pyskt_main = pyskt_main self.background = skia.Color4f(*[26 / 255] * 3 + [1])
def test_ColorFilter_filterColor4f(colorfilter): srcCS = skia.ColorSpace.MakeSRGB() dstCS = skia.ColorSpace.MakeSRGB() assert isinstance( colorfilter.filterColor4f(skia.Color4f(0xFF00FFFF), srcCS, dstCS), skia.Color4f)
def draw(self, notes_playing): away_ratio = 0.33 # If the note is black we leave a not filled spot on the bottom, this is the ratio of it away = (self.height * (away_ratio)) if self.is_black else 0 # Based on the away, center, width, height etc, get the coords of this piano key coords = [ self.center_x - (self.width / 2), self.resolution_height - self.height, self.center_x + (self.width / 2), self.resolution_height - away, ] white_or_back = "white" if self.is_white else "black" # Rectangle border key_rect = skia.RRect(skia.Rect(*coords), self.config["rounding"]["piano_keys"][white_or_back]["x"], self.config["rounding"]["piano_keys"][white_or_back]["y"] ) # Is the note active self.active = self.note in notes_playing.keys() if self.config["piano_key_follows_note_color"]: if self.active: channel = notes_playing[self.note] color1 = self.colors[f"channel_{channel}"]["plain_1"] color2 = self.colors[f"channel_{channel}"]["plain_2"] else: color1 = self.color_idle_1 color2 = self.color_idle_2 else: # Get the color based on if the note is active or not color1 = self.color_active_1 if self.active else self.color_idle_1 color2 = self.color_active_2 if self.active else self.color_idle_2 # Make the skia Paint and key_paint = skia.Paint( AntiAlias = True, # Color = color1, Style = skia.Paint.kFill_Style, Shader = skia.GradientShader.MakeLinear( points = [(0.0, self.mmvskia_main.context.height), (self.mmvskia_main.context.width, self.mmvskia_main.context.height)], colors = [color1, color2]), StrokeWidth = 0, ) # The border of the key key_border = skia.Paint( AntiAlias = True, Color = skia.Color4f(0, 0, 0, 1), Style = skia.Paint.kStroke_Style, StrokeWidth = 1, ) # Make flat top if self.is_black: bleed = self.config["rounding"]["piano_keys"][white_or_back]["x"] / 8 flat_top_key_rect = skia.Rect(coords[0], coords[1] - bleed, coords[2], (coords[3] - (coords[3] - coords[1]) / 4) - bleed) self.mmvskia_main.skia.canvas.drawRect(flat_top_key_rect, key_paint) # Draw the key (we'll draw on top of it later) self.mmvskia_main.skia.canvas.drawRRect(key_rect, key_paint) # Draw the 3d effect if (not self.active): # One fourth of the away ratio three_d_effect_away = away + (self.height * (away_ratio / 4)) # The coordinates considering black key's away three_d_effect_coords = [ self.center_x - (self.width / 2), self.resolution_height - three_d_effect_away, self.center_x + (self.width / 2), self.resolution_height - away, ] # The paint three_d_effect_paint = skia.Paint( AntiAlias = True, Color = self.color_3d_effect, Style = skia.Paint.kFill_Style, StrokeWidth = 0, ) rect = skia.RRect(skia.Rect(*three_d_effect_coords), 8, 8) # Draw the rectangle self.mmvskia_main.skia.canvas.drawRRect(rect, three_d_effect_paint) # Draw the border if not self.is_black: self.mmvskia_main.skia.canvas.drawRRect(key_rect, key_border)
def build(self, effects): # Clear the background self.mmvskia_main.skia.canvas.clear( skia.Color4f(*self.global_colors["background"], 1)) # Draw the orientation markers if self.config["do_draw_markers"]: self.draw_markers() # # Get "needed" variables time_first_note = self.vectorial.midi.time_first_note # If user passed seconds offset then don't use automatic one from midi file if "seconds_offset" in self.config.keys(): offset = self.config["seconds_offset"] else: offset = 0 # offset = time_first_note # .. if audio is trimmed? # Offsetted current time at the piano key top most part current_time = self.mmvskia_main.context.current_time - offset # What keys we'll bother rendering? Check against the first note time offset # That's because current_time should be the real midi key not the offsetted one accept_minimum_time = current_time - self.config[ "seconds_of_midi_content"] accept_maximum_time = current_time + self.config[ "seconds_of_midi_content"] # What notes are playing? So we draw a darker piano key self.notes_playing = [] # For each channel of notes for channel in self.vectorial.midi.timestamps.keys(): # For each key message on the timestamps of channels (notes) for key in self.vectorial.midi.timestamps[channel]: # A "key" is a note if it's an integer if isinstance(key, int): # This note play / stop times, for all notes [[start, end], [start, end] ...] note = key times = self.vectorial.midi.timestamps[channel][note][ "time"] delete = [] # For each note index and the respective interval for index, interval in enumerate(times): # Out of bounds completely, we don't care about this note anymore. # We mark for deletion otherwise it'll mess up the indexing if interval[1] < accept_minimum_time: delete.append(index) continue # Notes past this one are too far from being played and out of bounds if interval[0] > accept_maximum_time: break # Is the current time inside the note? If yes, the note is playing current_time_in_interval = (interval[0] < current_time < interval[1]) # Append to playing notes if current_time_in_interval: self.notes_playing.append(note) # Either way, draw the key self.draw_note( # TODO: No support for velocity yet :( velocity=128, # Vertical position (start / end) start=interval[0], end=interval[1], # Channel for the color and note for the horizontal position channel=channel, note=note, # Name so we can decide to draw a sharp or plain key name=self.vectorial.midi.note_to_name(note), ) # This is an interval we do not care about anymore # as the end of the note is past the minimum time we accept a note being rendered for index in reversed(delete): del self.vectorial.midi.timestamps[channel][note][ "time"][index] self.draw_piano()
def build(self, fitted_ffts: dict, frequencies: list, this_step: int, config: dict, effects): resolution_ratio_multiplier = self.vectorial.context.resolution_ratio_multiplier if self.config["mode"] == "symetric": data = {} for channel in (["l", "r"]): data[channel] = { "coordinates": [], "paints": [], } this_channel_fft = fitted_ffts[channel] npts = len(this_channel_fft) for index, magnitude in enumerate(this_channel_fft): if channel == "l": theta = (math.pi / 2) - ((index / npts) * math.pi) elif channel == "r": theta = (math.pi / 2) + ((index / npts) * math.pi) magnitude = (magnitude / 720) * self.context.height minimum_multiplier = 0.4 maximum_multiplier = 17 size = (magnitude) * self.functions.ax_plus_b_two_points( x=frequencies[0][index], end_x=20000, zero_value=minimum_multiplier, max_value=maximum_multiplier, ) # size = (magnitude*6) * ( (( (maximum_multiplier - minimum_multiplier)*index) / len(this_channel_fft)) + minimum_multiplier ) # We send an r, theta just in case we want to do something with it later on data[channel]["coordinates"].append([ # ( self.config["minimum_bar_size"] + magnitude*(2e6) ) * effects["size"], (self.config["minimum_bar_size"] + size) * effects["size"], theta, ]) # Rotate the colors a bit on each step theta += this_step / 100 # Define the color of the bars colors = [ abs(math.sin((theta / 2))), abs(math.sin((theta + ((1 / 3) * 2 * math.pi)) / 2)), abs(math.sin((theta + ((2 / 3) * 2 * math.pi)) / 2)), ] + [0.7] # Add full opacity # Make a skia color with the colors list as argument color = skia.Color4f(*colors) # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kStroke_Style, StrokeWidth=8 * resolution_ratio_multiplier # + (magnitude/4), ) # Store it on a list do draw in the end data[channel]["paints"].append(paint) # Our list of coordinates and paints, invert the right channel for drawing the path in the right direction # Not reversing it will yield "symetric" bars along the diagonal coordinates = data["l"]["coordinates"] + [ x for x in reversed(data["r"]["coordinates"]) ] paints = data["l"]["paints"] + [ x for x in reversed(data["r"]["paints"]) ] # Filled background if False: # self.config["draw_background"] path = skia.Path() white_background = skia.Paint( AntiAlias=True, Color=skia.ColorWHITE, Style=skia.Paint.kFill_Style, StrokeWidth=3, ImageFilter=skia.ImageFilters.DropShadow( 3, 3, 5, 5, skia.ColorBLACK), MaskFilter=skia.MaskFilter.MakeBlur( skia.kNormal_BlurStyle, 1.0)) more = 1.05 self.polar.from_r_theta(coordinates[0][0] * more, coordinates[0][1]) polar_offset = self.polar.get_rectangular_coordinates() path.moveTo( (self.center_x + polar_offset[0]), (self.center_y + polar_offset[1]), ) for coord_index, coord in enumerate(coordinates): # TODO: implement this function in DataUtils for not repeating myself get_nearby = 4 size_coordinates = len(coordinates) real_state = coordinates * 3 nearby_coordinates = real_state[size_coordinates + ( coord_index - get_nearby):size_coordinates + (coord_index + get_nearby)] # [0, 1, 2, 3, 4] --> weights= # 3 4 5, 4, 3 n = len(nearby_coordinates) weights = [n - abs((n / 2) - x) for x in range(n)] s = 0 for index, item in enumerate(nearby_coordinates): s += item[0] * weights[index] avg_coord = s / sum(weights) self.polar.from_r_theta(avg_coord * more, coord[1]) polar_offset = self.polar.get_rectangular_coordinates() path.lineTo( (self.center_x + polar_offset[0]), (self.center_y + polar_offset[1]), ) self.skia.canvas.drawPath(path, white_background) # Countour, stroke if False: # self.config["draw_black_border"] more = 2 path = skia.Path() black_stroke = skia.Paint( AntiAlias=True, Color=skia.ColorWHITE, Style=skia.Paint.kStroke_Style, StrokeWidth=6, ImageFilter=skia.ImageFilters.DropShadow( 3, 3, 5, 5, skia.ColorWHITE), MaskFilter=skia.MaskFilter.MakeBlur( skia.kNormal_BlurStyle, 1.0)) for coord_index, coord in enumerate(coordinates): get_nearby = 10 size_coordinates = len(coordinates) real_state = coordinates * 3 nearby_coordinates = real_state[size_coordinates + ( coord_index - get_nearby):size_coordinates + (coord_index + get_nearby)] n = len(nearby_coordinates) weights = [n - abs((n / 2) - x) for x in range(n)] s = 0 for index, item in enumerate(nearby_coordinates): s += item[0] * weights[index] avg_coord = s / sum(weights) self.polar.from_r_theta( self.config["minimum_bar_size"] + ((avg_coord - self.config["minimum_bar_size"]) * more), coord[1]) polar_offset = self.polar.get_rectangular_coordinates() coords = [(self.center_x + polar_offset[0]), (self.center_y + polar_offset[1])] if coord_index == 0: path.moveTo(*coords) path.lineTo(*coords) self.skia.canvas.drawPath(path, black_stroke) # Draw the main bars for index, coord in enumerate(coordinates): more = 1 path = skia.Path() path.moveTo(self.center_x, self.center_y) self.polar.from_r_theta(coord[0], coord[1]) polar_offset = self.polar.get_rectangular_coordinates() path.lineTo( (self.center_x + polar_offset[0]) * more, (self.center_y + polar_offset[1]) * more, ) self.skia.canvas.drawPath(path, paints[index])
def draw_note(self, velocity, start, end, channel, note, name): color_channels = { 0: { "plain": ImageColor.getcolor("#ffcc00", "RGB"), "sharp": ImageColor.getcolor("#ff9d00", "RGB"), }, 1: { "plain": ImageColor.getcolor("#00ff0d", "RGB"), "sharp": ImageColor.getcolor("#00a608", "RGB"), }, 2: { "plain": ImageColor.getcolor("#6600ff", "RGB"), "sharp": ImageColor.getcolor("#39008f", "RGB"), }, 3: { "plain": ImageColor.getcolor("#ff0000", "RGB"), "sharp": ImageColor.getcolor("#990000", "RGB"), }, 4: { "plain": ImageColor.getcolor("#00fffb", "RGB"), "sharp": ImageColor.getcolor("#00b5b2", "RGB"), }, 5: { "plain": ImageColor.getcolor("#ff006f", "RGB"), "sharp": ImageColor.getcolor("#a10046", "RGB"), }, 6: { "plain": ImageColor.getcolor("#aaff00", "RGB"), "sharp": ImageColor.getcolor("#75b000", "RGB"), }, 7: { "plain": ImageColor.getcolor("#e1ff00", "RGB"), "sharp": ImageColor.getcolor("#a9bf00", "RGB"), }, 8: { "plain": ImageColor.getcolor("#ff3300", "RGB"), "sharp": ImageColor.getcolor("#a82200", "RGB"), }, 9: { "plain": ImageColor.getcolor("#00ff91", "RGB"), "sharp": ImageColor.getcolor("#00b567", "RGB"), }, 10: { "plain": ImageColor.getcolor("#ff00aa", "RGB"), "sharp": ImageColor.getcolor("#c40083", "RGB"), }, 11: { "plain": ImageColor.getcolor("#c800ff", "RGB"), "sharp": ImageColor.getcolor("#8e00b5", "RGB"), }, 12: { "plain": ImageColor.getcolor("#00ff4c", "RGB"), "sharp": ImageColor.getcolor("#00c93c", "RGB"), }, 13: { "plain": ImageColor.getcolor("#ff8a8a", "RGB"), "sharp": ImageColor.getcolor("#bf6767", "RGB"), }, 14: { "plain": ImageColor.getcolor("#ffde7d", "RGB"), "sharp": ImageColor.getcolor("#c4aa5e", "RGB"), }, 15: { "plain": ImageColor.getcolor("#85ebff", "RGB"), "sharp": ImageColor.getcolor("#5ca7b5", "RGB"), }, 16: { "plain": ImageColor.getcolor("#ff7aa4", "RGB"), "sharp": ImageColor.getcolor("#bd5978", "RGB"), }, "default": { "plain": ImageColor.getcolor("#dddddd", "RGB"), "sharp": ImageColor.getcolor("#ffffff", "RGB"), }, } note_colors = color_channels.get(channel, color_channels["default"]) if "#" in name: width = self.semitone_width * 0.9 c = note_colors["sharp"] color = skia.Color4f(c[0] / 255, c[1] / 255, c[2] / 255, 1) else: width = self.tone_width * 0.6 c = note_colors["plain"] color = skia.Color4f(c[0] / 255, c[1] / 255, c[2] / 255, 1) # Make the skia Paint and paint = skia.Paint( AntiAlias=True, Color=color, Style=skia.Paint.kFill_Style, # Shader=skia.GradientShader.MakeLinear( # points=[(0.0, 0.0), (self.vectorial.context.width, self.vectorial.context.height)], # colors=[skia.Color4f(0, 0, 1, 1), skia.Color4f(0, 1, 0, 1)]), StrokeWidth=2, ) # c = ImageColor.getcolor("#d1ce1d", "RGB") c = (0, 0, 0) border = skia.Paint( AntiAlias=True, Color=skia.Color4f(c[0] / 255, c[1] / 255, c[2] / 255, 1), Style=skia.Paint.kStroke_Style, # ImageFilter=skia.ImageFilters.DropShadow(3, 3, 5, 5, skia.ColorBLUE), # MaskFilter=skia.MaskFilter.MakeBlur(skia.kNormal_BlurStyle, 2.0), StrokeWidth=max( self.vectorial.context.resolution_ratio_multiplier * 2, 1), ) x = self.keys_centers[note] y = self.functions.proportion( self.seconds_of_midi_content, self.viewport_height, #*2, self.vectorial.context.current_time - start) height = self.functions.proportion(self.seconds_of_midi_content, self.viewport_height, end - start) coords = [ x - (width / 2), y + (self.viewport_height) - height, x + (width / 2), y + (self.viewport_height), ] # Rectangle border rect = skia.Rect(*coords) # Draw the border self.skia.canvas.drawRect(rect, paint) self.skia.canvas.drawRect(rect, border)
def build(self, fftinfo, this_step, config, effects): c = self.background_color self.skia.canvas.clear(skia.Color4f(c, c, c, 1)) self.seconds_of_midi_content = config["seconds-of-midi-content"] SECS_OFFSET = config["seconds-offset"] self.draw_markers() # Get "needed" variables current_time = self.vectorial.context.current_time + SECS_OFFSET resolution_ratio_multiplier = self.vectorial.context.resolution_ratio_multiplier # contents = self.datautils.dictionary_items_in_between( # self.midi.timestamps, # current_time - 2, # current_time + self.seconds_of_midi_content * 2 # ) accept_minimum_time = current_time - self.seconds_of_midi_content accept_maximum_time = current_time + self.seconds_of_midi_content self.notes_playing = [] for channel in self.midi.timestamps.keys(): for key in self.midi.timestamps[channel]: if isinstance(key, int): note = key times = self.midi.timestamps[channel][note]["time"] delete = [] for index, interval in enumerate(times): # Out of bounds if interval[1] < accept_minimum_time: delete.append(index) continue if interval[0] > accept_maximum_time: break current_time_in_interval = (interval[0] < current_time < interval[1]) accepted_render = (accept_minimum_time < current_time < accept_maximum_time) if current_time_in_interval: self.notes_playing.append(note) if current_time_in_interval or accepted_render: self.draw_note( velocity=128, start=interval[0] - SECS_OFFSET, end=interval[1] - SECS_OFFSET, channel=channel, note=note, name=self.midi.note_to_name(note), ) for index in reversed(delete): del self.midi.timestamps[channel][note]["time"][index] self.draw_piano()
def skia(self): if skia: return skia.Color4f(self.r, self.g, self.b, self.a) else: raise Exception("Skia installation not found")
def next(self, fftinfo: dict, this_step: int, skia_canvas=None) -> None: self.current_step += 1 # Animation has ended, this current_animation isn't present on path.keys if self.current_animation not in list(self.animation.keys()): self.is_deletable = True return # The animation we're currently playing this_animation = self.animation[self.current_animation] animation = this_animation["animation"] steps = animation["steps"] # The current step is one above the steps we've been told, next animation if self.current_step == steps + 1: self.current_animation += 1 self.current_step = 0 return # Reset offset, pending self.offset = [0, 0] self.image.pending = {} self.image.reset_to_original_image() self._reset_effects_variables() position = this_animation["position"] path = position["path"] if "modules" in this_animation: modules = this_animation["modules"] self.is_vectorial = "vectorial" in modules # The video module must be before everything as it gets the new frame if "video" in modules: this_module = modules["video"] # We haven't set a video capture or it has ended if self.video is None: self.video = cv2.VideoCapture(this_module["path"]) # Can we read next frame? if not, go back to frame 0 for a loop ok, frame = self.video.read() if not ok: # cry self.video.set(cv2.CAP_PROP_POS_FRAMES, 0) ok, frame = self.video.read() # CV2 utilizes BGR matrix, but we need RGB frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) shake = 0 for modifier in position: if self.mmv.utils.is_matching_type([modifier], [MMVModifierShake]): shake = modifier.distance width = self.mmv.context.width + (4 * shake) height = self.mmv.context.height + (4 * shake) self.image.load_from_array(frame) self.image.resize_to_resolution(width, height, override=True) if "rotate" in modules: this_module = modules["rotate"] rotate = this_module["object"] amount = rotate.next() amount = round(amount, self.ROUND) if not self.is_vectorial: self.image.rotate(amount, from_current_frame=True) else: self.rotate_value = amount if "resize" in modules: this_module = modules["resize"] resize = this_module["object"] # Where the vignetting intensity is pointing to according to our resize.next(fftinfo["average_value"]) self.size = resize.get_value() if not self.is_vectorial: # If we're going to rotate, resize the rotated frame which is not the original image offset = self.image.resize_by_ratio( self.size, from_current_frame=True) if this_module["keep_center"]: self.offset[0] += offset[0] self.offset[1] += offset[1] # DONE if "blur" in modules: this_module = modules["blur"] blur = this_module["object"] blur.next(fftinfo["average_value"]) amount = blur.get_value() self.image_filters.append( skia.ImageFilters.Blur(amount, amount)) if "fade" in modules: this_module = modules["fade"] fade = this_module["object"] fade.next(fftinfo["average_value"]) self.image.transparency(fade.get_value()) # Apply vignetting if "vignetting" in modules: this_module = modules["vignetting"] vignetting = this_module["object"] # Where the vignetting intensity is pointing to according to our vignetting.next(fftinfo["average_value"]) vignetting.get_center() next_vignetting = vignetting.get_value() # This is a somewhat fake vignetting, we just start a black point with full transparency # at the center and make a radial gradient that is black with no transparency at the radius skia_canvas.canvas.drawPaint({ 'Shader': skia.GradientShader.MakeRadial( center=(vignetting.center_x, vignetting.center_y), radius=next_vignetting, colors=[ skia.Color4f(0, 0, 0, 0), skia.Color4f(0, 0, 0, 1) ]) }) if "vectorial" in modules: this_module = modules["vectorial"] vectorial = this_module["object"] effects = { "size": self.size, "rotate": self.rotate_value, "image_filters": self.image_filters, } # Visualizer blit itself into the canvas automatically vectorial.next(fftinfo, this_step, effects) # Iterate through every position module for modifier in path: # # Override modules argument = [self.x, self.y] + self.offset # Move according to a Point (be stationary) if self.mmv.utils.is_matching_type([modifier], [MMVModifierPoint]): # Attribute (x, y) to Point's x and y [self.x, self.y], self.offset = modifier.next(*argument) # Move according to a Line (interpolate current steps) if self.mmv.utils.is_matching_type([modifier], [MMVModifierLine]): # Interpolate and unpack next coordinate [self.x, self.y], self.offset = modifier.next(*argument) # # Offset modules # Get next shake offset value if self.mmv.utils.is_matching_type([modifier], [MMVModifierShake]): [self.x, self.y], self.offset = modifier.next(*argument)